Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transform SoapFault to SoapMessage via Interceptor in CXF?

I have web-service created and configured via Spring and CXF. See beans below:

<?xml version="1.0" encoding="UTF-8"?>
<beans <!-- ommited -->>
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="internalActService" class="package.InternalActServiceImpl" />

    <jaxws:endpoint implementor="#internalActService" address="/InternalActService">
        <jaxws:properties>
            <entry key="schema-validation-enabled" value="true" />
        </jaxws:properties>

        <jaxws:outFaultInterceptors>
            <bean class="package.InternalActServiceFaultOutInterceptor" />
        </jaxws:outFaultInterceptors>
    </jaxws:endpoint>
</beans>

As can you see I added schema validation to my web service. But CXF throws SoapFault when request is not corresponding with schema. I want to send to the client SoapMessage instead of SoapFault, that's why I added outFaultInterceptors.

My question is how to transform SoapFault to SoapMessage? I've made few tries but I don't know how to implement outFaultInterceptor.

like image 574
Michał Mech Avatar asked Nov 09 '11 14:11

Michał Mech


People also ask

How do I generate a class from WSDL using Apache CXF?

You will have to make sure that you create an appropriate directory structure for your project and add the earlier shown hello. wsdl file to the specified folder. The wsdl2java plugin will compile this wsdl and create Apache CXF classes in a pre-defined folder.

What is CXF interceptor?

Interceptors are the fundamental processing unit inside CXF. When a service is invoked, an InterceptorChain is created and invoked. Each interceptor gets a chance to do what they want with the message. This can include reading it, transforming it, processing headers, validating the message, etc.

What is CXF endpoint?

In Apache Camel, the Camel CXF component is the key to integrating routes with Web services. You can use the Camel CXF component to create a CXF endpoint, which can be used in either of the following ways: Consumer — (at the start of a route) represents a Web service instance, which integrates with the route.

What is Apache CXF used for?

Apache CXF™ is an open source services framework. CXF helps you build and develop services using frontend programming APIs, like JAX-WS and JAX-RS. These services can speak a variety of protocols such as SOAP, XML/HTTP, RESTful HTTP, or CORBA and work over a variety of transports such as HTTP, JMS or JBI.


2 Answers

Probably you forgot to setup interceptor phase and its order in the interceptor chain.

Try something like this:

package org.foo.bar;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.AttachmentOutInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.StaxOutInterceptor;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

import java.util.Arrays;
import java.util.List;

public class InternalActServiceFaultOutInterceptor extends AbstractSoapInterceptor {

    public InternalActServiceFaultOutInterceptor() {
        super(Phase.PRE_STREAM);
        addBefore(Arrays.asList(StaxOutInterceptor.class.getName(), AttachmentOutInterceptor.class.getName()));
    }

    @Override
    public void handleMessage(SoapMessage message) throws Fault {
        Exception exception = message.getContent(Exception.class);
        if(exception != null) {
            message.getExchange().put(Exception.class, null);

            for(Class<?> contentFormat : message.getContentFormats()) {
                message.setContent(contentFormat, null);
            }

            message.setContent(List.class, new MessageContentsList(createSoapMessage(RegisterDocumentResponse.class)));
        }
    }

    protected <T> T createSoapMessage(Class<T> messageType) {
        // create your message
        return null;
    }

}

-EDIT-

Here is a unit test that works for me. It's a little bit tricky to be able to send POJOs to the output. I suppose it can be much more simpler if constructing DOM by yourself.

Interceptor

package foo.bar;

import java.util.Arrays;
import java.util.List;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.InterceptorChain;
import org.apache.cxf.interceptor.OutgoingChainInterceptor;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.model.BindingMessageInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.service.model.MessageInfo;
import org.apache.cxf.service.model.OperationInfo;
import org.apache.cxf.service.model.ServiceModelUtil;
import org.apache.cxf.ws.policy.PolicyOutInterceptor;

public class InternalActServiceFaultOutInterceptor extends AbstractSoapInterceptor {

    public InternalActServiceFaultOutInterceptor() {
        super(Phase.SETUP);
        addBefore(Arrays.asList(PolicyOutInterceptor.class.getName()));
    }

    @Override
    public void handleMessage(SoapMessage message) throws Fault {
        Exchange exchange = message.getExchange();

        resetOrigInterceptorChain(message);
        resetFault(exchange);

        Message outMessage = createOutMessage(exchange);

        InterceptorChain chain = prepareNewInterceptorChain(exchange);
        chain.doIntercept(outMessage);
    }

    private InterceptorChain prepareNewInterceptorChain(Exchange exchange) {
        Message message = exchange.getOutMessage();
        bind(message);

        InterceptorChain chain = OutgoingChainInterceptor.getOutInterceptorChain(exchange);
        message.setInterceptorChain(chain);

        return chain;
    }

    private Message createOutMessage(Exchange exchange) {
        Endpoint ep = exchange.get(Endpoint.class);

        Message outMessage = ep.getBinding().createMessage();
        outMessage.setExchange(exchange);
        outMessage.setContent(List.class, new MessageContentsList(createSoapMessage()));

        exchange.setOutMessage(outMessage);
        return outMessage;
    }

    private void resetFault(Exchange exchange) {
        exchange.put(Exception.class, null);
    }

    private void resetOrigInterceptorChain(SoapMessage message) {
        InterceptorChain chain = message.getInterceptorChain();
        for(Interceptor<?> interceptor : chain) {
            chain.remove(interceptor);
        }
        chain.reset();
    }

    private void bind(Message message) {
        Exchange exchange = message.getExchange();
        BindingOperationInfo bop = unwrap(message.getExchange().getBindingOperationInfo());

        message.put(MessageInfo.class, bop.getOperationInfo().getOutput());
        message.put(BindingMessageInfo.class, bop.getOutput());

        bop = unwrap(ServiceModelUtil.getOperationForWrapperElement(exchange, bop.getName(), false));

        exchange.put(BindingOperationInfo.class, bop);
        if (bop != null) {
            exchange.put(BindingOperationInfo.class, bop);
            exchange.put(OperationInfo.class, bop.getOperationInfo());
        }
    }

    private BindingOperationInfo unwrap(BindingOperationInfo bop) {
        while(bop.getUnwrappedOperation() != null) {
            bop = bop.getUnwrappedOperation();
            return bop;
        }
        return bop;
    }

    protected Echo createSoapMessage() {
        Echo e = new Echo();
        e.setValue("Bye World!");
        return e;
    }

}

Request/Response POJO

package foo.bar;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EchoType")
public class Echo {
    private String value;
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

WebService

package foo.bar;

import javax.jws.WebService;

@WebService
public class InternalActServiceImpl {
    public Echo echo(Echo val) {
        return val;
    }
}

Spring Context: CxfInterceptorTest-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxws.xml" />

    <bean id="internalActService" class="foo.bar.InternalActServiceImpl" />

    <jaxws:endpoint implementor="#internalActService" address="http://localhost:9080/InternalActService">
        <jaxws:properties>
            <entry key="schema-validation-enabled" value="true" />
        </jaxws:properties>
        <jaxws:outFaultInterceptors>
            <bean class="foo.bar.InternalActServiceFaultOutInterceptor" />
        </jaxws:outFaultInterceptors>
    </jaxws:endpoint>

Unit Test

package foo.bar;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CxfInterceptorTest {

    private static final String REQ =
            "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:bar=\"http://bar.foo/\">\r\n" +
            "   <soapenv:Header/>\r\n" +
            "   <soapenv:Body>\r\n" +
            "      <bar:echo>\r\n" +
            "         <arg0>\r\n" +
            "            <value1>Hello World</value1>\r\n" +
            "         </arg0>\r\n" +
            "      </bar:echo>\r\n" +
            "   </soapenv:Body>\r\n" +
            "</soapenv:Envelope>";

    @Test
    public void validate() throws Exception {
        String s = call();
        Assert.assertTrue(s.contains("Bye World!"));
    }

    private String call() throws Exception {
        URL url = new URL("http://localhost:9080/InternalActService");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setDoOutput(true);
        conn.setInstanceFollowRedirects(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
        conn.setRequestProperty("SOAPAction", "");

        OutputStream os = conn.getOutputStream();
        os.write(REQ.getBytes());
        os.flush();
        os.close();

        final int buffSize = 1024;
        byte[] buff = new byte[1024];
        InputStream is = conn.getInputStream();

        StringBuilder builder = new StringBuilder(buffSize);
        for(int readBytes = -1; (readBytes = is.read(buff, 0, buffSize)) != -1; ) {
            builder.append(new String(buff, 0, readBytes));
        }

        is.close();

        return builder.toString();
    }

}
like image 171
szhem Avatar answered Nov 13 '22 00:11

szhem


Your interceptor should implement

org.apache.cxf.interceptor.Interceptor

The handleFault of handleMessage method will be called. The parameter is both cases will be an instance of

org.apache.cxf.message.Message

You can call on that

removeContent()

or

setContent()

to replace the message.

like image 21
Peter Szanto Avatar answered Nov 12 '22 22:11

Peter Szanto