001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.cxfbc.interceptors;
018    
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import javax.xml.XMLConstants;
025    import javax.xml.namespace.QName;
026    import javax.xml.stream.XMLStreamException;
027    import javax.xml.stream.XMLStreamReader;
028    import javax.xml.transform.Source;
029    import javax.xml.transform.dom.DOMSource;
030    
031    import org.w3c.dom.Document;
032    import org.w3c.dom.Element;
033    import org.w3c.dom.NamedNodeMap;
034    import org.w3c.dom.Node;
035    import org.w3c.dom.NodeList;
036    
037    import org.apache.cxf.binding.soap.Soap11;
038    import org.apache.cxf.binding.soap.Soap12;
039    import org.apache.cxf.binding.soap.SoapHeader;
040    import org.apache.cxf.binding.soap.SoapMessage;
041    import org.apache.cxf.binding.soap.SoapVersion;
042    import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
043    import org.apache.cxf.binding.soap.model.SoapBindingInfo;
044    import org.apache.cxf.binding.soap.model.SoapHeaderInfo;
045    import org.apache.cxf.endpoint.Endpoint;
046    import org.apache.cxf.headers.Header;
047    import org.apache.cxf.helpers.DOMUtils;
048    import org.apache.cxf.interceptor.Fault;
049    import org.apache.cxf.message.Exchange;
050    import org.apache.cxf.message.Message;
051    import org.apache.cxf.phase.Phase;
052    import org.apache.cxf.service.model.BindingMessageInfo;
053    import org.apache.cxf.service.model.BindingOperationInfo;
054    import org.apache.cxf.service.model.MessagePartInfo;
055    import org.apache.cxf.service.model.SchemaInfo;
056    import org.apache.cxf.staxutils.DepthXMLStreamReader;
057    import org.apache.cxf.staxutils.PartialXMLStreamReader;
058    import org.apache.cxf.staxutils.StaxUtils;
059    import org.apache.servicemix.soap.util.DomUtil;
060    import org.apache.servicemix.soap.util.stax.StaxSource;
061    
062    /**
063     * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
064     */
065    public class JbiInWsdl1Interceptor extends AbstractSoapInterceptor {
066    
067        private boolean useJBIWrapper = true;
068        private boolean useSOAPEnvelope = true;
069    
070        public JbiInWsdl1Interceptor(boolean useJBIWrapper, boolean useSOAPEnvelope) {
071            super(Phase.PRE_INVOKE);
072            addAfter(JbiOperationInterceptor.class.getName());
073            this.useJBIWrapper = useJBIWrapper;
074            this.useSOAPEnvelope = useSOAPEnvelope;
075        }
076    
077        public void handleMessage(SoapMessage message) {
078            // Ignore faults messages
079            if (message.getContent(Exception.class) != null) {
080                return;
081            }
082            Document document = DomUtil.createDocument();
083                  
084            if (!useJBIWrapper) {
085                    SoapVersion soapVersion = message.getVersion();
086                    if (useSOAPEnvelope) {
087                            Element soapEnv = DomUtil.createElement(document, new QName(
088                        soapVersion.getEnvelope().getNamespaceURI(), soapVersion
089                                .getEnvelope().getLocalPart(), soapVersion
090                                .getPrefix()));
091                            Element soapBody = DomUtil.createElement(soapEnv, new QName(
092                        soapVersion.getBody().getNamespaceURI(), soapVersion
093                                .getBody().getLocalPart(), soapVersion
094                                .getPrefix()));
095                            setExtraPrefix(message, soapEnv);
096                            
097                    soapEnv.appendChild(soapBody);
098                            Element body = getBodyElement(message);
099                            
100                            if (body != null) {
101                                    soapBody.appendChild(soapBody.getOwnerDocument().importNode(body,
102                                                    true));
103                            }
104                    } else {
105                            Element body = getBodyElement(message);
106                            if (body != null) {
107                            document.appendChild(document.importNode(body, true));
108                    }
109                    }
110                    message.setContent(Source.class, new DOMSource(document));
111            } else {
112                try {
113                    XMLStreamReader reader = new StaxJbiWrapper(message);
114                    message.setContent(Source.class, new StaxSource(reader));
115                } catch (UnsupportedOperationException e) {
116                    document = createDOMWrapper(message);
117                    if (document != null) {
118                        message.setContent(Source.class, new DOMSource(document));
119                    }
120                }
121            }
122            
123        }
124    
125        private void setExtraPrefix(SoapMessage message, Element soapEnv) {
126            Document savedEnv = (Document) message.getContent(Node.class);
127            if (savedEnv != null) {
128                NamedNodeMap attrs = savedEnv.getFirstChild().getAttributes();
129                Map<String, String> nsMap = message.getEnvelopeNs();
130                if (nsMap == null) {
131                    nsMap = new HashMap<String, String>();
132                }
133                for (int i = 0; i < attrs.getLength(); i++) {
134                    Node node = attrs.item(i);
135                    if (!node.getNodeValue().equals(Soap11.SOAP_NAMESPACE)
136                            && !node.getNodeValue().equals(Soap12.SOAP_NAMESPACE)) {
137                        //set extra prefix
138                        soapEnv.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
139                                + node.getLocalName(),
140                                node.getNodeValue());
141                        nsMap.put(node.getLocalName(), node.getNodeValue());
142                    }
143                        
144                }
145                if (nsMap.size() > 0) {
146                    message.put("soap.env.ns.map", nsMap);
147                }
148            }
149        }
150    
151        private Document createDOMWrapper(SoapMessage message) {
152            Document document;
153            BindingOperationInfo wsdlOperation = getOperation(message);
154            BindingMessageInfo wsdlMessage = !isRequestor(message) ? wsdlOperation
155                    .getInput()
156                    : wsdlOperation.getOutput();
157    
158            document = DomUtil.createDocument();
159            Element root = DomUtil.createElement(document,
160                    CxfJbiConstants.WSDL11_WRAPPER_MESSAGE);
161            String typeNamespace = wsdlMessage.getMessageInfo().getName()
162                    .getNamespaceURI();
163            if (typeNamespace == null || typeNamespace.length() == 0) {
164                throw new IllegalArgumentException(
165                        "messageType namespace is null or empty");
166            }
167            root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
168                            + CxfJbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
169                            typeNamespace);
170            
171            setExtraPrefix(message, root);
172            
173            String typeLocalName = wsdlMessage.getMessageInfo().getName()
174                    .getLocalPart();
175            if (typeLocalName == null || typeLocalName.length() == 0) {
176                throw new IllegalArgumentException(
177                        "messageType local name is null or empty");
178            }
179            root.setAttribute(CxfJbiConstants.WSDL11_WRAPPER_TYPE,
180                    CxfJbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX + ":"
181                            + typeLocalName);
182            String messageName = wsdlMessage.getMessageInfo().getName()
183                    .getLocalPart();
184            root.setAttribute(CxfJbiConstants.WSDL11_WRAPPER_NAME, messageName);
185            root.setAttribute(CxfJbiConstants.WSDL11_WRAPPER_VERSION, "1.0");
186    
187            SoapBindingInfo binding = (SoapBindingInfo) message.getExchange()
188                    .get(Endpoint.class).getEndpointInfo().getBinding();
189            String style = binding.getStyle(wsdlOperation.getOperationInfo());
190            if (style == null) {
191                style = binding.getStyle();
192            }
193    
194            Element body = getBodyElement(message);
195            if (body == null) {
196                return null;
197            }
198    
199            if (body.getLocalName().equals("Fault")) {
200                handleJBIFault(message, body);
201                return null;
202            }
203            List<SoapHeaderInfo> headers = wsdlMessage
204                    .getExtensors(SoapHeaderInfo.class);
205            List<Header> headerElement = message.getHeaders();
206            List<Object> parts = new ArrayList<Object>();
207            for (MessagePartInfo part : wsdlMessage.getMessageParts()) {
208                if ("document".equals(style)) {
209                    parts.add(body);
210                } else /* rpc-style */ {
211                    // SOAP:Body element is the operation name, children are
212                    // operation parameters
213    
214                    Element param = DomUtil.getFirstChildElement(body);
215                    boolean found = false;
216                    while (param != null) {
217                        if (part.getName().getLocalPart().equals(
218                                param.getLocalName())) {
219                            found = true;
220                            parts.add(wrapNodeList(param.getChildNodes()));
221                            break;
222                        }
223                        param = DomUtil.getNextSiblingElement(param);
224                    }
225                    if (!found) {
226                        throw new Fault(new Exception("Missing part '"
227                                + part.getName() + "'"));
228                    }
229                }
230            }
231            processHeader(message, headers, headerElement, parts);
232            for (Object part : parts) {
233                if (part instanceof Node) {
234                    addPart(root, (Node) part);
235                } else if (part instanceof NodeList) {
236                    addPart(root, (NodeList) part);
237                } else if (part instanceof SoapHeader) {
238                    addPart(root, (Node) ((SoapHeader) part).getObject());
239                }
240            }
241            return document;
242        }
243    
244        private void processHeader(SoapMessage message,
245                List<SoapHeaderInfo> headers, List<Header> headerElement,
246                List<Object> parts) {
247            if (headers != null) {
248                for (SoapHeaderInfo header : headers) {
249                    MessagePartInfo part = header.getPart();
250                    Header param = findHeader(headerElement, part);
251                    int idx = part.getIndex();
252                    if (idx > parts.size()) {
253                        parts.add(param);
254                    } else if (idx == -1) {
255                        parts.add(0, param);
256                    } else {
257                        parts.add(idx, param);
258                    }
259                }
260            }
261        }
262    
263        private void handleJBIFault(SoapMessage message, Element soapFault) {
264            try {
265                Document doc = DomUtil.createDocument();
266                Element jbiFault = DomUtil.createElement(doc, new QName(
267                        CxfJbiConstants.WSDL11_WRAPPER_NAMESPACE,
268                        JbiFault.JBI_FAULT_ROOT));
269                Node jbiFaultDetail = null;
270                if (message.getVersion() instanceof Soap11) {
271                    NodeList nodeList = soapFault.getElementsByTagName("faultcode");
272                    String faultCode = nodeList.item(0).getFirstChild()
273                            .getTextContent();
274                    String prefix = "";
275                    if (faultCode.indexOf(":") != -1) {
276                        prefix = faultCode.substring(0, faultCode.indexOf(":"));
277                    }
278                    String localName = faultCode
279                            .substring(faultCode.indexOf(":") + 1);
280                    message.put("faultcode", new QName(prefix, localName));
281                    nodeList = soapFault.getElementsByTagName("faultstring");
282                    message.put("faultstring", nodeList.item(0).getFirstChild()
283                            .getTextContent());
284                    nodeList = soapFault.getElementsByTagName("detail");
285                    if (nodeList != null
286                            && nodeList.getLength() > 0
287                            && DomUtil.getFirstChildElement(nodeList.item(0)) != null) {
288                        jbiFaultDetail = doc.importNode(DomUtil
289                                .getFirstChildElement(nodeList.item(0)), true);
290                    } else {
291                        message.put("hasdetail", false);
292                        nodeList = soapFault.getElementsByTagName("faultstring");
293                        jbiFaultDetail = doc.importNode(nodeList.item(0)
294                                .getFirstChild(), true);
295                    }
296    
297                } else {
298                    NodeList nodeList = soapFault.getElementsByTagName("soap:Code");
299                    String faultCode = DomUtil.getFirstChildElement(
300                            nodeList.item(0)).getTextContent();
301                    String prefix = "";
302                    if (faultCode.indexOf(":") != -1) {
303                        prefix = faultCode.substring(0, faultCode.indexOf(":"));
304                    }
305                    String localName = faultCode
306                            .substring(faultCode.indexOf(":") + 1);
307                    message.put("faultcode", new QName(prefix, localName));
308                    nodeList = soapFault.getElementsByTagName("soap:Reason");
309                    message.put("faultstring", DomUtil.getFirstChildElement(
310                            nodeList.item(0)).getTextContent());
311                    nodeList = soapFault.getElementsByTagName("soap:Detail");
312                    if (nodeList != null
313                            && nodeList.getLength() > 0
314                            && DomUtil.getFirstChildElement(nodeList.item(0)) != null) {
315                        jbiFaultDetail = doc.importNode(DomUtil
316                                .getFirstChildElement(nodeList.item(0)), true);
317                    } else {
318                        message.put("hasdetail", false);
319                        nodeList = soapFault.getElementsByTagName("faultstring");
320                        jbiFaultDetail = doc.importNode(DomUtil
321                                .getFirstChildElement(nodeList.item(0)), true);
322                    }
323    
324                }
325    
326                SchemaInfo schemaInfo = getOperation(message).getBinding()
327                        .getService().getSchema(jbiFaultDetail.getNamespaceURI());
328                if (schemaInfo != null && !schemaInfo.isElementFormQualified()
329                        && (jbiFaultDetail instanceof Element)) {
330                    // that's unquailied fault
331                    jbiFaultDetail = addEmptyDefaultTns((Element) jbiFaultDetail);
332                }
333                jbiFault.appendChild(jbiFaultDetail);
334                message.setContent(Source.class, new DOMSource(doc));
335                message.put("jbiFault", true);
336            } catch (Exception e) {
337                String eMessage = e.getMessage();
338                if (eMessage == null) {
339                    eMessage = e.getClass().getName();
340                }
341                throw new RuntimeException("the fault message can't be parsed:" + eMessage);
342                
343            }
344        }
345    
346        private Element addEmptyDefaultTns(Element ret) {
347            
348            if (!ret.hasAttribute("xmlns")) {
349                ret.setAttribute("xmlns", "");
350            }
351            NodeList nodes = ret.getChildNodes();
352            for (int i = 0; i < nodes.getLength(); i++) {
353                if (nodes.item(i) instanceof Element) {
354                    Element ele = (Element) nodes.item(i);
355                    ele = addEmptyDefaultTns(ele);
356    
357                }
358            }
359            return ret;
360        }
361    
362        
363        private NodeList wrapNodeList(final NodeList childNodes) {
364            return new NodeList() {
365                public int getLength() {
366                    return childNodes.getLength();
367                }
368    
369                public Node item(int index) {
370                    return childNodes.item(index);
371                }
372            };
373        }
374    
375        protected BindingOperationInfo getOperation(Message message) {
376            BindingOperationInfo operation = message.getExchange().get(
377                    BindingOperationInfo.class);
378            if (operation == null) {
379                throw new Fault(
380                        new Exception("Operation not bound on this message"));
381            }
382            return operation;
383        }
384    
385        /**
386         * Extract the content as DOM element
387         */
388        protected Element getBodyElement(SoapMessage message) {
389            try {
390                XMLStreamReader xmlReader = message
391                        .getContent(XMLStreamReader.class);
392                XMLStreamReader filteredReader = new PartialXMLStreamReader(
393                        xmlReader, message.getVersion().getBody());
394                //ensure the whitespace is passed
395                StaxUtils.toNextElement((DepthXMLStreamReader) filteredReader);
396                Document doc = DOMUtils.createDocument();
397                StaxUtils.readDocElements(doc, filteredReader, true);
398                return doc.getDocumentElement();
399            } catch (XMLStreamException e) {
400                throw new Fault(e);
401            }
402        }
403    
404        protected Header getHeaderElement(SoapMessage message, QName name) {
405            Exchange exchange = message.getExchange();
406            BindingOperationInfo bop = exchange.get(BindingOperationInfo.class);
407            if (bop.isUnwrapped()) {
408                bop = bop.getWrappedOperation();
409            }
410            boolean client = isRequestor(message);
411            BindingMessageInfo bmi = client ? bop.getOutput() : bop.getInput();
412            if (bmi == null) {
413                // one way operation.
414                return null;
415            }
416            List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
417            if (headers == null || headers.size() == 0) {
418                return null;
419            }
420            List<Header> headerElement = message.getHeaders();
421            for (SoapHeaderInfo header : headers) {
422                QName qname = header.getPart().isElement() 
423                    ? header.getPart().getElementQName() : header.getPart().getTypeQName();
424                if (qname.equals(name)) {
425                    MessagePartInfo mpi = header.getPart();
426                    return findHeader(headerElement, mpi);
427                }
428            }
429            return null;
430        }
431    
432        /**
433         * Add a jbi:part to a normalized message document
434         */
435        private static void addPart(Element parent, Node partValue) {
436            Element element = DomUtil.createElement(parent,
437                    CxfJbiConstants.WSDL11_WRAPPER_PART);
438            element.appendChild(element.getOwnerDocument().importNode(partValue,
439                    true));
440        }
441    
442        /**
443         * Add a jbi:part to a normalized message document
444         */
445        private static void addPart(Element parent, NodeList nodes) {
446            Element element = DomUtil.createElement(parent,
447                    CxfJbiConstants.WSDL11_WRAPPER_PART);
448            for (int i = 0; i < nodes.getLength(); i++) {
449                Node node = nodes.item(i);
450                element.appendChild(element.getOwnerDocument().importNode(node,
451                        true));
452            }
453        }
454    
455        private static Header findHeader(List<Header> headerElement,
456                MessagePartInfo mpi) {
457            Header param = null;
458            if (headerElement != null) {
459                QName name = mpi.getConcreteName();
460                for (Header header : headerElement) {
461                    if (mpi.isElement()) {
462                        if (header.getName().getNamespaceURI() != null
463                            && header.getName().getNamespaceURI().equals(
464                                    name.getNamespaceURI())
465                            && header.getName().getLocalPart() != null
466                            && header.getName().getLocalPart().equals(
467                                    name.getLocalPart())) {
468                            param = header;
469                        }
470                    } else {
471                        if (header.getName().getLocalPart() != null
472                                && header.getName().getLocalPart().equals(
473                                    name.getLocalPart())) {
474                            param = header;
475                        }
476                    }
477                }
478            }
479            return param;
480        }
481        
482    }