Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing a XML not keeping duplicated namespaces in the parent node and child node

Before I start: I know that the child node inherits the namespace from the parent node and that's why my problem occurs. Unfortunately, the webservice I am sending my XML doesn't accept the child node without the namespace and, as it is a government entity, a change in their part is rather unlikely.

That being said, I am using Spring-WS to make the communication between my application and the webservice, so in one way or the other the framework uses a transformer to parse my payload Source to the framework's payload Result:

transformer.transform(Source, Result);

Before that transformation take place, my XML has these two nodes like it follows here:

<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="3.10">
   <NFe xmlns="http://www.portalfiscal.inf.br/nfe">

After the transformation, the second namespace is removed(as I said before, I know the reason):

<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="3.10">
   <NFe>

I am also aware that I can use marshallers to achieve the same result and writing the parse code myself. Using that approach is also ok and would be acceptable, but I don't know any other way to achieve the same thing (transforming the javax.xml.transform.Source into javax.xml.transform.Result) using other approach besides the one listed above.

I have two questions then:

1 - Can I avoid the behaviour I am having with the default approach(without using marshallers)?

2 - Is there any other tool that would make the same transformation?

like image 297
Diego Urenia Avatar asked Jan 21 '16 12:01

Diego Urenia


1 Answers

I've been through the same trouble. Unfortunately (or not) WebServiceTemplate with implementation of SOAPMessageFactory (such as SaajSoapMessageFactory) will do everything possible to assure you are sending a well-formed XML as a request by tying you to the Transformers from Source to Result, including never let you repeat 'xmlns' in children when you already did in parent. You have a couple of elegant options to try - what doesn't mean they're the simplest ones. You can work at XML level by using javax.xml.ws.Service and Dispatch interface, which is quite easy if you don't need SSL authentication. Check these links out (first one is written in Pt-BR):

http://www.guj.com.br/t/nfe-v2-00-veja-como-consumir-o-ws/297304

https://alesaudate.wordpress.com/2010/08/09/how-to-dynamically-select-a-certificate-alias-when-invoking-web-services/

Also you can try another message factory, such as DomPoxMessageFactory. This link might be useful:

http://forum.spring.io/forum/spring-projects/web-services/128221-webservicetemplate-get-it-to-stop-adding-soap-envelope

However, if changing the structure of your project isn't an option (which was my case), I have a workaround for you. Yes, a workaround, but once the target webservice IS EXPECTING a malformed XML, I absolve myself :D

I just created abstractions of HttpComponentsMessageSender and HttpComponentsConnection classes, the second one is instantiated through the first one's method createConnection(URI uri). So I can create my WebServiceTemplate like this:

WebServiceTemplate wst = new WebServiceTemplate(new SaajSoapMessageFactory());
wst.setMessageSender(new CustomHttpComponentsMessageSender());

Sadly you'll need to reply the createConnecion method to the new abstraction just to instantiate the custom connection. As I said, it's a workaround!

@Override
public WebServiceConnection createConnection(URI uri) throws IOException {
    HttpPost httpPost = new HttpPost(uri);
    if (isAcceptGzipEncoding()) {
        httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
                HttpTransportConstants.CONTENT_ENCODING_GZIP);
    }
    HttpContext httpContext = createContext(uri);
    return new CustomHttpComponentsConnection(getHttpClient(), httpPost, httpContext);
}

The message is effectively sent inside the method onSendAfterWrite(WebServiceMessage message) of the HttpComponentsConnection class I'm abstracting from. Surprisingly, the 'message' parameter isn't used inside the method. It's there only for inheritance rules. And the good news: It's a protected method. The downside, again, is that I need to copy almost the entire class in order to change only this method, once the fields has no public visibility, and framework will need them in response handling. So, I'll post my entire class down:

public class CustomHttpComponentsConnection extends HttpComponentsConnection {

    private final HttpClient httpClient;

    private final HttpPost httpPost;

    private final HttpContext httpContext;

    private HttpResponse httpResponse;

    private ByteArrayOutputStream requestBuffer;

    protected CustomHttpComponentsConnection(HttpClient httpClient, HttpPost httpPost, HttpContext httpContext) {
        super(httpClient, httpPost, httpContext);

        Assert.notNull(httpClient, "httpClient must not be null");
        Assert.notNull(httpPost, "httpPost must not be null");
        this.httpClient = httpClient;
        this.httpPost = httpPost;
        this.httpContext = httpContext;
    }

    public HttpResponse getHttpResponse() {
    return httpResponse;
    }

    public HttpPost getHttpPost() {
        return httpPost;
    }

    @Override
    protected OutputStream getRequestOutputStream() throws IOException {
        return requestBuffer;
    }

    @Override
    protected void onSendBeforeWrite(WebServiceMessage message) throws IOException {
        requestBuffer = new ByteArrayOutputStream();
    }

    @Override
    protected void onSendAfterWrite(WebServiceMessage message) throws IOException {

        OutputStream out = getRequestOutputStream();

        String str = out.toString();

        str = str.replaceAll("<NFe>", "<NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\">");
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        bs.write(str.getBytes());

        getHttpPost().setEntity(new ByteArrayEntity(bs.toByteArray()));

        requestBuffer = null;
        if (httpContext != null) {
            httpResponse = httpClient.execute(httpPost, httpContext);
        }
        else {
            httpResponse = httpClient.execute(httpPost);
        }
    }

    @Override
    protected int getResponseCode() throws IOException {
        return httpResponse.getStatusLine().getStatusCode();
    }

    @Override
    protected String getResponseMessage() throws IOException {
        return httpResponse.getStatusLine().getReasonPhrase();
    }

    @Override
    protected long getResponseContentLength() throws IOException {
        HttpEntity entity = httpResponse.getEntity();
        if (entity != null) {
            return entity.getContentLength();
        }
        return 0;
    }

    @Override
    protected InputStream getRawResponseInputStream() throws IOException {
        HttpEntity entity = httpResponse.getEntity();
        if (entity != null) {
             return entity.getContent();
        }
        throw new IllegalStateException("Response has no enclosing response entity, cannot create input stream");
    }

    @Override
    public Iterator<String> getResponseHeaderNames() throws IOException {
        Header[] headers = httpResponse.getAllHeaders();
        String[] names = new String[headers.length];
        for (int i = 0; i < headers.length; i++) {
            names[i] = headers[i].getName();
        }
        return Arrays.asList(names).iterator();
    }

    @Override
    public Iterator<String> getResponseHeaders(String name) throws IOException {
        Header[] headers = httpResponse.getHeaders(name);
        String[] values = new String[headers.length];
        for (int i = 0; i < headers.length; i++) {
            values[i] = headers[i].getValue();
        }
        return Arrays.asList(values).iterator();
    }

Again, this is the easiest way I found when changing project structure is not an option. Hope this helps.

like image 74
Daniel Chaves Avatar answered Sep 28 '22 06:09

Daniel Chaves