Spring ClientInterceptor shows empty SOAP header in response - java

I have a SOAP service which I need to talk to. The first SOAP request receives a response with a sessionID element in the SOAP header which I need to send in the final release command.
To grab this sessionID value I plan on using a ClientInterceptor. In my implementation of WebServiceGatewaySupport I register my interceptor:
this.setInterceptors(new ClientInterceptor[] { new MyWebServiceClientInterceptor() });
My Interceptor:
public class MyWebServiceClientInterceptor implements ClientInterceptor {
public final QName SessionID_QNAME = new QName("http://xml.example.com/ws/session", "sessionID");
public boolean handleFault(MessageContext context) throws WebServiceClientException {
logger.info("Handle Fault");
return true;
}
public boolean handleResponse(MessageContext context) throws WebServiceClientException {
logger.info("Handle Response");
SoapMessage soapMessage = (SoapMessage) context.getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
logger.info("Response Header: " + soapHeader.getName());
Iterator<SoapHeaderElement> qn = soapHeader.examineAllHeaderElements();
while (qn.hasNext()) {
SoapElement elem = qn.next();
logger.info(elem.toString());
}
Iterator<QName> an = soapHeader.getAllAttributes();
while (an.hasNext()) {
QName elem = an.next();
logger.info(elem.toString());
}
return true;
}
public boolean handleRequest(MessageContext context) throws WebServiceClientException {
logger.info("Handle Request");
return true;
}
}
However I keep getting back an empty SOAP header. I'm running Wireshark and can clearly see the sessionID token in the returned SOAP packet so at a lost as to what's going on. Any ideas?

I'm an idiot. 'Twas a typo. I needed to call:
SoapMessage soapMessage = (SoapMessage) context.getResponse();
Not:
SoapMessage soapMessage = (SoapMessage) context.getRequest();
Full code for those wanting to set headers on requests and receive headers on responses:
ClientInterceptor to receive custom SOAP header values:
public class MyWebServiceClientInterceptor implements ClientInterceptor {
public boolean handleFault(MessageContext context) throws WebServiceClientException {
return true;
}
public boolean handleResponse(MessageContext context) throws WebServiceClientException {
SoapMessage soapMessage = (SoapMessage) context.getResponse();
SoapHeader soapHeader = soapMessage.getSoapHeader();
Iterator<SoapHeaderElement> qn = soapHeader.examineHeaderElements(MY_SESSIONID_QNAME);
while (qn.hasNext()) {
SoapElement elem = qn.next();
SoapHeaderElement headerElem = (SoapHeaderElement) elem;
if (StringUtil.validString(headerElem.getText())) {
if (!headerElem.getText().equals(sessionId)) {
sessionId = headerElem.getText();
logger.info("Bound to Session ID: " + sessionId);
}
}
}
return true;
}
public boolean handleRequest(MessageContext context) throws WebServiceClientException {
return true;
}
}
Where I use the interceptor:
public class MySoapClient extends WebServiceGatewaySupport {
public MySoapClient() {
this.setInterceptors(new ClientInterceptor[] { new MyWebServiceClientInterceptor() });
...
}
...
}
Callback to set custom SOAP Header:
class MySoapActionCallback implements WebServiceMessageCallback {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
SoapHeaderElement messageId = soapMessage.getSoapHeader().addHeaderElement(SessionID_QNAME);
if (StringUtil.validString(sessionId)) {
messageId.setText(sessionId);
}
}
}
Where I use the callback:
JAXBElement<ReturnType> result = (JAXBElement<ReturnType>) getWebServiceTemplate().marshalSendAndReceive(of.createRelease(null), new MySoapActionCallback());

Related

SOAP security, client authentication problem

I tried to connect to TERYT but I get an error about the lack of authorization.
WSDL address - https://uslugaterytws1test.stat.gov.pl/wsdl/terytws1.wsdl
java.lang.IllegalStateException: Failed to execute ApplicationRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:765) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.1.jar:2.7.1]
at com.example.teryt.TerytApplication.main(TerytApplication.java:15) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_332]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_332]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_332]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_332]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.7.1.jar:2.7.1]
Caused by: com.sun.xml.internal.ws.fault.ServerSOAPFaultException: Client received SOAP Fault from server: An error occurred when processing the security tokens in the message. Please see the server log to find more detail regarding exact cause of the failure.
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:178) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:116) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.StubHandler.readResponse(StubHandler.java:238) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:189) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:276) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:104) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:77) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:147) ~[na:1.8.0_332]
at com.sun.proxy.$Proxy83.pobierzListeWojewodztw(Unknown Source) ~[na:na]
at com.example.teryt.TerytClient.getResponse(TerytClient.java:31) ~[classes/:na]
at com.example.teryt.TerytApplication.lambda$lookup$0(TerytApplication.java:20) [classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) [spring-boot-2.7.1.jar:2.7.1]
This is what the correct query should look like:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/">
soapenv:Header
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-2018-01-11T00:16:02+00:00">
wsse:UsernameTestPubliczny</wsse:Username>
wsse:Password1234abcd</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
<wsa:Action xmlns:wsa="http://www.w3.org/2005/08/addressing">http://tempuri.org/ITerytWs1/PobierzListeWojewodztw</wsa:Action>
</soapenv:Header>
soapenv:Body
ns1:PobierzListeWojewodztw
ns1:DataStanu2020-05-18</ns1:DataStanu>
</ns1:PobierzListeWojewodztw>
</soapenv:Body>
</soapenv:Envelope>
TerytApplication.java:
#SpringBootApplication
public class TerytApplication {
public static void main(String[] args) {
SpringApplication.run(TerytApplication.class, args);
}
#Bean
ApplicationRunner lookup(TerytClient terytClient){
return args -> System.out.println(terytClient.getResponse());
}
}
Client.java:
#Component
public class TerytClient extends WebServiceGatewaySupport {
public Boolean getResponse() throws DatatypeConfigurationException {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
XMLGregorianCalendar xCal = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(cal);
ITerytWs1 instance = new TerytWs1().getCustom(new AddressingFeature(true));
Binding binding = ((BindingProvider) instance).getBinding();
List<Handler> handlerList = binding.getHandlerChain();
if (handlerList == null)
handlerList = new ArrayList<Handler>();
handlerList.add(new TerytHeaderHandler("TestPubliczny", "1234abcd"));
binding.setHandlerChain(handlerList);
System.out.println(instance.pobierzListeWojewodztw(xCal));
return instance.czyZalogowany();
}
HeaderHandler.java
#Override
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
SOAPElement security = header.addChildElement("Security", "wsse",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
SOAPElement username = usernameToken.addChildElement("Username", "wsse");
username.addTextNode(wsUser);
SOAPElement password = usernameToken.addChildElement("Password", "wsse");
password.setAttribute("Type",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
password.addTextNode(wsPassword);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//This handler does nothing with the response from the Web Service
//even though it should probably check its mustUnderstand headers
SOAPMessage message = smc.getMessage();
}
return outboundProperty;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
// TODO Auto-generated method stub
return false;
}
#Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
// Gets the header blocks that can be processed by this Handler instance.
#Override
public Set<QName> getHeaders() {
QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security");
HashSet<QName> headers = new HashSet<QName>();
headers.add(securityHeader);
return headers;
}
}
I would be grateful for any help and suggestions.
When building the WS-Security headers, I recommend you to use Apache WSS4J. I am attaching a code example that I use frequently.
import lombok.extern.slf4j.Slf4j;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.message.WSSecHeader;
import org.apache.wss4j.dom.message.WSSecTimestamp;
import org.apache.wss4j.dom.message.WSSecUsernameToken;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
#Slf4j
public class ClientWSSecurityUsernameTokenHandler implements
SOAPHandler<SOAPMessageContext> {
public ClientWSSecurityUsernameTokenHandler(
String userName, String password) {
_userName = userName;
_password = password;
}
#Override
public Set<QName> getHeaders() {
final String NAMESPACE_URI =
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
final String LOCAL_PART = "Security";
final String PREFIX = "wsse";
final QName wsSecurity = new QName(NAMESPACE_URI, LOCAL_PART, PREFIX);
final Set<QName> headers = new HashSet<QName>();
headers.add(wsSecurity);
return headers;
}
#Override
public boolean handleMessage(SOAPMessageContext soapMessageContext) {
Boolean outboundProperty = (Boolean) soapMessageContext.get(
MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
try {
SOAPMessage soapMessage = soapMessageContext.getMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPHeader soapHeader = soapMessage.getSOAPHeader();
if (null == soapHeader) {
SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
soapHeader = soapEnvelope.addHeader();
}
WSSecHeader wsSecHeader = new WSSecHeader(soapPart);
Element securityElement =
wsSecHeader.insertSecurityHeader();
_addUsernamePassword(soapMessageContext, soapPart, wsSecHeader);
WSSecTimestamp wsSecTimeStamp = new WSSecTimestamp(wsSecHeader);
wsSecTimeStamp.build();
_appendSecurityHeader(soapHeader, securityElement);
_logSOAPMessage(soapMessageContext.getMessage());
}
catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
}
return true;
}
private String getPassword() {
return _password;
}
private String getUserName() {
return _userName;
}
#Override
public boolean handleFault(SOAPMessageContext soapMessageContext) {
_logSOAPMessage(soapMessageContext.getMessage());
return true;
}
#Override
public void close(MessageContext messageContext) {
}
private String _userName;
private String _password;
private void _appendSecurityHeader(
SOAPHeader soapHeader, Element securityElement) {
soapHeader.removeChild(securityElement);
soapHeader.appendChild(securityElement);
}
private void _addUsernamePassword(
SOAPMessageContext soapMessageContext, SOAPPart soapPart,
WSSecHeader wsSecHeader) {
log.debug("Adding Username Token for the user: {}", getUserName());
WSSecUsernameToken usernameToken = new WSSecUsernameToken(wsSecHeader);
usernameToken.setUserInfo(getUserName(), getPassword());
usernameToken.setPasswordType(WSConstants.PASSWORD_TEXT);
usernameToken.addCreated();
usernameToken.addNonce();
usernameToken.build();
}
private void _logSOAPMessage(SOAPMessage message) {
if (log.isDebugEnabled()) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
message.writeTo(bout);
String msg = bout.toString("UTF-8");
log.debug("Start of SOAP Message");
log.debug(msg);
log.debug("End of SOAP Message");
}
catch (SOAPException | IOException exception) {
if (log.isErrorEnabled()) {
log.error(exception.getMessage(), exception);
}
}
}
}
}
Source Code 1 - SOAP Handler ClientWSSecurityUsernameTokenHandler
The following code instead shows how to add the handler to the chain.
ClientWSSecurityUsernameTokenHandler
clientWSSSecurityUsernameTokenHandler =
new ClientWSSecurityUsernameTokenHandler(soapClientProperties.getUsername(),
soapClientProperties.getPassword());
List<Handler> handlerChain = List.of(clientWSSSecurityUsernameTokenHandler);
((BindingProvider) serviceSOAP).getBinding().setHandlerChain(handlerChain);
Source Code 2 - Add the ClientWSSecurityUsernameTokenHandler to SOAP Handler Chain
The following code instead shows how to add the wss4j-ws-security-dom as dependency.
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-dom</artifactId>
<version>2.4.1</version>
</dependency>

Spring WS MTOM : change boundary name

Spring ws with mtom is forcing the "Content-Type" header of the response to :
Multipart/Related; boundary="----=_Part_9_1258512392.1655296193519"; type="application/xop+xml"; start-info="text/xml"
The issue here is I have to force the boundary value to
--uuid:something
I am using a ClientInterceptor class to override the Content-Type Header :
public class RequestHandler implements EndpointInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) {
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) {
this.setMTOM(messageContext);
return true;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) {
}
private void setMTOM(MessageContext messageContext) {
String uri;
try {
uri = TransportContextHolder.getTransportContext().getConnection().getUri().toString();
} catch (URISyntaxException e) {
throw new LocalizedStatusException(INTERNAL_SERVER_ERROR, "messages.error_parsing_uri");
}
if (!uri.contains("no-mtom")) {
String headerUUID = UUID.randomUUID().toString().strip();
SaajSoapMessage response = (SaajSoapMessage) messageContext.getResponse();
response.getSaajMessage().getSOAPPart().setContentId("<rootpart*" + headerUUID + "#example.jaxws.sun.com>");
response.getSaajMessage().getMimeHeaders().setHeader(
HEADER_CONTENT_TYPE,
"Multipart/Related; boundary=\"uuid:" + headerUUID + "\"; start=\"<rootpart*" + headerUUID + "#example.jaxws.sun.com>\"; type=\"application/xop+xml\"; start-info=\"text/xml\""
);
response.getSaajMessage().getSOAPPart().setMimeHeader(
HEADER_CONTENT_TRANSFER_ENCODING,
"binary"
);
}
}
}
But the SaajSoapMessage object have a "saveChanges()" method which is called after the interceptor.
This method is overriding this Content-Type header.
Is there any way to force this header ?
Thank you in advance, and sorry for my poor english.

Add CDATA on request's string parameter using only JAX-WS

I have a jax-ws client ganerated with CXF
The request have a string-parameter (MGRequest) that contains an xml, all work's but the generated request is like this:
<S:Body>
<ns5:MGRequest><mytag>hello</mytag></ns5:MGRequest>
</S:Body>
I need to generate the body like:
<S:Body>
<ns5:MGRequest><![CDATA[<mytag>hello</mytag>]]></ns5:MGRequest>
</S:Body>
(because i can't control the server..)
The client is like a standard jax-ws:
#WebService(name = "ServiceSoap")
#XmlSeeAlso({ ObjectFactory.class})
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface ServiceSoap {
#WebMethod(operationName = "ProcessMessage")
#WebResult(name = "MGResponse")
public String processMessage(
#WebParam(partName = "input", name = "MGRequest") String input);
}
And i call like this:
Service client = new Service(url);
client.setHandlerResolver(HandlerFactory.build(new LoggerHandler()));
ServiceSoap service = client.getServiceSoap();
String msgToSend = JaxbUtil.jaxbObjToString(xmlObj, false);
String response = service.processMessage(msgToSend);
I have tried adding #XmlJavaTypeAdapter(CDataAdapter.class) before #WebParam but the result was:
<S:Body>
<ns5:MGRequest><![CDATA[<mytag>hello</mytag>]]></ns5:MGRequest>
</S:Body>
Where CDataAdapter:
public class CDataAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String v) throws Exception {
return "<![CDATA[" + v + "]]>";
}
#Override
public String unmarshal(String v) throws Exception {
return v;
}
}
Any idea how to archive that?
Thanks
After a working night i've found the solution:
adding a javax.xml.ws.handler.Handler to the client like this:
client.setHandlerResolver(HandlerFactory.build(new LoggerHandler(), new CDataHandler()));
where my HandlerFactory build a Handler:
public static HandlerResolver build(final Handler... handlers) {
return new HandlerResolver() {
#Override
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerChain = new ArrayList<Handler>();
if (handlers != null) {
for (Handler handler : handlers) {
handlerChain.add(handler);
}
}
return handlerChain;
}
};
}
import javax.xml.namespace.QName;
import javax.xml.soap.Node;
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;
public class CDataHandler implements SOAPHandler<SOAPMessageContext> {
#Override
public void close(MessageContext context) {
}
#Override
public boolean handleMessage(SOAPMessageContext soapMessage) {
try {
SOAPMessage message = soapMessage.getMessage();
boolean isOutboundMessage = (Boolean) soapMessage
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// is a request?
if (isOutboundMessage) {
// build a CDATA NODE with the text in the root tag
Node cddata = (Node) message.getSOAPPart().createCDATASection(
message.getSOAPBody().getFirstChild().getTextContent());
// add the CDATA's node at soap message
message.getSOAPBody().getFirstChild().appendChild(cddata);
// remove the text tag with the raw text that will be escaped
message.getSOAPBody().getFirstChild()
.removeChild(message.getSOAPBody().getFirstChild().getFirstChild());
}
} catch (Exception ex) {
// fail
}
return true;
}
#Override
public boolean handleFault(SOAPMessageContext soapMessage) {
return true;
}
#Override
public Set<QName> getHeaders() {
return Collections.EMPTY_SET;
}
}
This is a simple class, i had only one tag with text, but in more complex scenario you can take the necessary action navigating the DOM.

Server Sent Event Client with additional Cookie

I am trying to unit test a Server Sent Event resource with an additional cookie. I am already using Jersey for the EventSource and JavaX for the client. The following code works fine:
WebTarget target = ClientBuilder.newBuilder()
.register(SseFeature.class)
.build()
.target("http://localhost:8080/sse");
EventSource eventSource = EventSource.target(target).build();
EventListener listener = new EventListener() {
#Override
public void onEvent(InboundEvent inboundEvent) {
LOG.info(inboundEvent.readData(String.class));
}
};
eventSource.register(listener);
eventSource.open();
serverEventManager.send("/sse", "foo");
eventSource.close();
Hoewever, for an additional unit test I need to add an additional cookie to the request. I have already tryed the following
target.(...).request.cookie("foo", "bar");
But this returns a builder from which I can't create the required WebTarget for the EventSource.
Here's what's happens within EventSource to establish a conection to the given WebTarget:
private Invocation.Builder prepareHandshakeRequest() {
Invocation.Builder request = EventSource.this.target
.request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE });
if ((this.lastEventId != null) && (!(this.lastEventId.isEmpty()))) {
request.header("Last-Event-ID", this.lastEventId);
}
if (EventSource.this.disableKeepAlive) {
request.header("Connection", "close");
}
return request;
}
As we can see - no chance to add a cookie here.
So WebTarget.request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE }) needs to return a Builder that
already has that desired cookie added.
Consider this delegating class adding the desired cookie to all request* type methods:
public class CookieAddedWebTarget implements WebTarget {
private WebTarget base;
private Cookie cookie;
public CookieAddedWebTarget(WebTarget base, Cookie cookie) {
this.base = base;
this.cookie = cookie;
}
// Inject that cookie whenever someone requests a Builder (like EventSource does):
public Builder request() {
return base.request().cookie(cookie);
}
public Builder request(String... paramArrayOfString) {
return base.request(paramArrayOfString).cookie(cookie);
}
public Builder request(MediaType... paramArrayOfMediaType) {
return base.request(paramArrayOfMediaType).cookie(cookie);
}
public Configuration getConfiguration() {
return base.getConfiguration();
}
//All other methods from WebTarget are delegated as-is:
public URI getUri() {
return base.getUri();
}
public UriBuilder getUriBuilder() {
return base.getUriBuilder();
}
public WebTarget path(String paramString) {
return base.path(paramString);
}
public WebTarget matrixParam(String paramString, Object... paramArrayOfObject) {
return base.matrixParam(paramString, paramArrayOfObject);
}
public WebTarget property(String paramString, Object paramObject) {
return base.property(paramString, paramObject);
}
public WebTarget queryParam(String paramString, Object... paramArrayOfObject) {
return base.queryParam(paramString, paramArrayOfObject);
}
public WebTarget register(Class<?> paramClass, Class<?>... paramArrayOfClass) {
return base.register(paramClass, paramArrayOfClass);
}
public WebTarget register(Class<?> paramClass, int paramInt) {
return base.register(paramClass, paramInt);
}
public WebTarget register(Class<?> paramClass, Map<Class<?>, Integer> paramMap) {
return base.register(paramClass, paramMap);
}
public WebTarget register(Class<?> paramClass) {
return base.register(paramClass);
}
public WebTarget register(Object paramObject, Class<?>... paramArrayOfClass) {
return base.register(paramObject, paramArrayOfClass);
}
public WebTarget register(Object paramObject, int paramInt) {
return base.register(paramObject, paramInt);
}
public WebTarget register(Object paramObject, Map<Class<?>, Integer> paramMap) {
return base.register(paramObject, paramMap);
}
public WebTarget register(Object paramObject) {
return base.register(paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject) {
return base.resolveTemplate(paramString, paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject, boolean paramBoolean) {
return base.resolveTemplate(paramString, paramObject, paramBoolean);
}
public WebTarget resolveTemplateFromEncoded(String paramString, Object paramObject) {
return base.resolveTemplateFromEncoded(paramString, paramObject);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap) {
return base.resolveTemplates(paramMap);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap, boolean paramBoolean) {
return base.resolveTemplates(paramMap, paramBoolean);
}
public WebTarget resolveTemplatesFromEncoded(Map<String, Object> paramMap) {
return base.resolveTemplatesFromEncoded(paramMap);
}
}
Now you should be able to reerite your test to:
EventSource eventSource = EventSource.target(new CookieAddedWebTarget(target,
new Cookie("name", "value"))).build();
And the cookie should be inserted.
Caveat: I have no way of testing this. Solution based only on reading source-code of jersey-media-sse-2.22.1.
Good Luck.
You could set the cookie in a ClientRequestFilter. Though the getCookies() on the ClientRequestContext is immutable, you should remember that a cookie is technically nothing more than a header. And the headers map on the request context is mutable. So you could do something like
public static class SseCookieFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
Cookie cookie = new Cookie("foo", "bar");
requestContext.getHeaders().add("Cookie", cookie.toString());
}
}
Just register the filter with the client (client.register(new SseCookieFilter())). It would be the same result as if you were to do
target.(...).request().cookie("foo", "bar");
Here is a complete example using Jersey Test Framework
public class SseCookieFilterTest extends JerseyTest {
#Path("events")
public static class SseResource {
#GET
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents(#CookieParam("foo") String foo) {
final EventOutput eventOutput = new EventOutput();
new Thread(() -> {
try {
final OutboundEvent.Builder eventBuilder
= new OutboundEvent.Builder();
eventBuilder.name("message");
eventBuilder.data(String.class, "Blah " + foo + "!!!");
final OutboundEvent event = eventBuilder.build();
eventOutput.write(event);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
eventOutput.close();
} catch (IOException ioClose) {
throw new RuntimeException(ioClose);
}
}
}).start();
return eventOutput;
}
}
public static class SseCookieFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
Cookie cookie = new Cookie("foo", "bar");
requestContext.getHeaders().add("Cookie", cookie.toString());
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(SseResource.class)
.register(new LoggingFilter());
}
#Test
public void doit() throws Exception {
Client client = ClientBuilder.newBuilder()
.register(SseFeature.class).build();
client.register(new SseCookieFilter());
WebTarget target = client.target("http://localhost:9998/events");
EventSource eventSource = EventSource.target(target).build();
EventListener listener = (InboundEvent inboundEvent) -> {
System.out.println("From server ---====++++> "
+ inboundEvent.readData(String.class));
};
eventSource.register(listener, "message");
eventSource.open();
Thread.sleep(100);
eventSource.close();
}
}
These are the only dependencies needed to test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
Here is the server side result from the LoggingFilter I registered on the server in the test
INFO: 1 * Server has received a request on thread grizzly-http-server-2
1 > GET http://localhost:9998/events
1 > accept: text/event-stream
1 > connection: close
1 > cookie: $Version=1;foo=bar
1 > host: localhost:9998
1 > user-agent: Jersey/2.19 (HttpUrlConnection 1.8.0_31)
INFO: 1 * Server responded with a response on thread grizzly-http-server-2
1 < 200
1 < Content-Type: text/event-stream
From server ---====++++> Blah bar!!!

Not able to pass a primitive value to my custom SOAPHandler class

I have written custom soaphandler class which extends javax.xml.rpc.GenericHandler. And my requirement is to pass a primitive variable into this handler class from my other caller java class. This variable should pass at run time and should be thread safe because multiple thread is going to access that handler same time.
I tried to store the value into HandlerConfig object and injected it to the HandlerInfo, but I couldn't found this value in my handler.
This Soap Handler concept is new for me, so please help me to fix this issue.
below I'm posting my raw code for handler class and the class from where I'm calling it.
public class MilerHandler extends GenericHandler {
private HandlerInfo info;
private static final String AUTHORIZATION = "Authorization";
private static final String DATE = "Date";
private static final String URI = "http://-----.com";
public MilerHandler() {
}
public void init(HandlerInfo info) {
this.info = info;
}
public QName[] getHeaders() {
return info.getHeaders();
}
public boolean handleRequest(MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPFactory factory = SOAPFactory.newInstance();
SOAPElement authorization = factory.createElement(AUTHORIZATION, PCMilerClientService.PREFIX, URI);
SOAPElement date = factory.createElement(DATE, PCMilerClientService.PREFIX, URI);
authorization.addTextNode((String)value1); //Value1 need to be pass from my business class.
date.addTextNode((int)value2); //Value2 need to be pass from my business class.
SOAPElement authHeader = factory.createElement(PCMilerClientService.AUTH_HEADER, PCMilerClientService.PREFIX, URI);
authHeader.addChildElement(authorization);
authHeader.addChildElement(date);
SOAPHeader header = envelope.getHeader();
header.addChildElement(authHeader);
message.saveChanges();
if(log.debug()) {
log.debug(message);
}
}
catch (Exception ex) {
log.error(ex);
}
return true;
}
public boolean handleResponse(javax.xml.rpc.handler.MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
if(log.debug()) {
log.debug(message);
}
return true;
}
public boolean handleFault(javax.xml.rpc.handler.MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
if(log.debug()) {
log.debug(message);
}
return true;
}
}
public class MilerDistanceLookupWorker {
public void run() {
IService_Stub stub = null;
Service_Impl impl = null;
try {
impl = new Service_Impl();
setPCMilerHandler(impl);
stub = (IService_Stub) impl.getBasicHttpBinding_IService();
} catch (ServiceException e) {
-----------------
}
}
private void setMilerHandler(Service_Impl impl) {
HandlerInfo handlerInfo = new HandlerInfo();
handlerInfo.setHandlerClass(MilerHandler.class);
QName authHeader = new QName(NAMESPACE, AUTH_HEADER, PREFIX);
List<HandlerInfo> headerList = impl.getHandlerRegistry().getHandlerChain(authHeader);
headerList.add(handlerInfo);
impl.getHandlerRegistry().setHandlerChain(authHeader, headerList);
}
}
If you are trying to implement SOAP auth handler then you need to do something like this:
public class SOAPAuthenticationHandler implements SOAPHandler<SOAPMessageContext> {
private String username;
private String password;
public SOAPAuthenticationHandler (String username, String password) {
this.username = username;
this.password = password;
}
#Override
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!outboundProperty) {
return outboundProperty;
}
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
SOAPElement security = header.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
security.addNamespaceDeclaration("common", "some xmlns");
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
usernameToken.addAttribute(new QName("xmlns:wsu"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
SOAPElement usernameElement = usernameToken.addChildElement("Username", "wsse");
usernameElement.addTextNode(username);
SOAPElement passwordElement = usernameToken.addChildElement("PasswordSaltedDigest", "common");
passwordElement.setAttribute("Type", "http://www.w3.org/2001/04/xmldsig-more#gostr341194");
passwordElement.addTextNode(password);
} catch (SOAPException | DOMException e) {
getLogger().error(e.getMessage());
}
return outboundProperty;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
return false;
}
#Override
public void close(MessageContext context) {
}
#Override
public Set<QName> getHeaders() {
return null;
}
}
And after that you need to add this handler to port' HandlerChain:
List handlerChain = ((BindingProvider) port).getBinding().getHandlerChain();
if (handlerChain == null) {
handlerChain = new ArrayList();
}
handlerChain.add(new SOAPAuthenticationHandler(username, password));
((BindingProvider) port).getBinding().setHandlerChain(handlerChain);

Categories

Resources