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