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            Document doc = DomUtil.createDocument();
227            Element jbiFault = DomUtil.createElement(doc, new QName(
228                    JBIConstants.NS_JBI_BINDING, JBIFault.JBI_FAULT_ROOT));
229            Node jbiFaultDetail = null;
230            if (message.getVersion() instanceof Soap11) {
231                NodeList nodeList = soapFault.getElementsByTagName("faultcode");
232                String faultCode = nodeList.item(0).getFirstChild().getTextContent();
233                String prefix = faultCode.substring(0, faultCode.indexOf(":"));
234                String localName = faultCode.substring(faultCode.indexOf(":") + 1);
235                message.put("faultcode", new QName(prefix, localName));
236                nodeList = soapFault.getElementsByTagName("faultstring");
237                message.put("faultstring", nodeList.item(0).getFirstChild().getTextContent());
238                nodeList = soapFault.getElementsByTagName("detail");
239                if (nodeList != null && nodeList.getLength() > 0 
240                        && nodeList.item(0).getFirstChild() != null) {
241                    jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
242                } else {
243                    message.put("hasdetail", false);
244                    nodeList = soapFault.getElementsByTagName("faultstring");
245                    jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
246                }
247                
248            } else {
249                NodeList nodeList = soapFault.getElementsByTagName("soap:Code");
250                String faultCode = nodeList.item(0).getFirstChild().getTextContent();
251                String prefix = faultCode.substring(0, faultCode.indexOf(":"));
252                String localName = faultCode.substring(faultCode.indexOf(":") + 1);
253                message.put("faultcode", new QName(prefix, localName));
254                nodeList = soapFault.getElementsByTagName("soap:Reason");
255                message.put("faultstring", nodeList.item(0).getFirstChild().getTextContent());
256                nodeList = soapFault.getElementsByTagName("soap:Detail");
257                if (nodeList != null && nodeList.getLength() > 0
258                        && nodeList.item(0).getFirstChild() != null) {
259                    jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
260                } else {
261                    message.put("hasdetail", false);
262                    nodeList = soapFault.getElementsByTagName("faultstring");
263                    jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
264                }
265                
266            }
267            SchemaInfo schemaInfo = 
268                getOperation(message).getBinding().getService().getSchema(jbiFaultDetail.getNamespaceURI());
269            if (schemaInfo != null && !schemaInfo.isElementFormQualified()) {
270                //that's unquailied fault
271                jbiFaultDetail = addEmptyDefaultTns((Element)jbiFaultDetail);
272                
273            }
274            jbiFault.appendChild(jbiFaultDetail);
275            message.setContent(Source.class, new DOMSource(doc));
276            message.put("jbiFault", true);
277        }
278    
279        private Element addEmptyDefaultTns(Element ret) {
280            
281            if (!ret.hasAttribute("xmlns")) {
282                ret.setAttribute("xmlns", "");
283            }
284            NodeList nodes = ret.getChildNodes();
285            for (int i = 0; i < nodes.getLength(); i++) {
286                if (nodes.item(i) instanceof Element) {
287                    Element ele = (Element) nodes.item(i);
288                    ele = addEmptyDefaultTns(ele);
289    
290                }
291            }
292            return ret;
293        }
294    
295        
296        private NodeList wrapNodeList(final NodeList childNodes) {
297            return new NodeList() {
298                public int getLength() {
299                    return childNodes.getLength();
300                }
301    
302                public Node item(int index) {
303                    return childNodes.item(index);
304                }
305            };
306        }
307    
308        protected BindingOperationInfo getOperation(Message message) {
309            BindingOperationInfo operation = message.getExchange().get(
310                    BindingOperationInfo.class);
311            if (operation == null) {
312                throw new Fault(
313                        new Exception("Operation not bound on this message"));
314            }
315            return operation;
316        }
317    
318        /**
319         * Extract the content as DOM element
320         */
321        protected Element getBodyElement(SoapMessage message) {
322            try {
323                XMLStreamReader xmlReader = message
324                        .getContent(XMLStreamReader.class);
325                XMLStreamReader filteredReader = new PartialXMLStreamReader(
326                        xmlReader, message.getVersion().getBody());
327                //ensure the whitespace is passed
328                StaxUtils.toNextElement((DepthXMLStreamReader) filteredReader);
329                Document doc = DOMUtils.createDocument();
330                StaxUtils.readDocElements(doc, filteredReader, false);
331                return doc.getDocumentElement();
332            } catch (XMLStreamException e) {
333                throw new Fault(e);
334            }
335        }
336    
337        protected Header getHeaderElement(SoapMessage message, QName name) {
338            Exchange exchange = message.getExchange();
339            BindingOperationInfo bop = exchange.get(BindingOperationInfo.class);
340            if (bop.isUnwrapped()) {
341                bop = bop.getWrappedOperation();
342            }
343            boolean client = isRequestor(message);
344            BindingMessageInfo bmi = client ? bop.getOutput() : bop.getInput();
345            if (bmi == null) {
346                // one way operation.
347                return null;
348            }
349            List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
350            if (headers == null || headers.size() == 0) {
351                return null;
352            }
353            List<Header> headerElement = message.getHeaders();
354            for (SoapHeaderInfo header : headers) {
355                QName qname = header.getPart().isElement() 
356                    ? header.getPart().getElementQName() : header.getPart().getTypeQName();
357                if (qname.equals(name)) {
358                    MessagePartInfo mpi = header.getPart();
359                    return findHeader(headerElement, mpi);
360                }
361            }
362            return null;
363        }
364    
365        /**
366         * Add a jbi:part to a normalized message document
367         */
368        private static void addPart(Element parent, Node partValue) {
369            Element element = DomUtil.createElement(parent,
370                    JbiConstants.WSDL11_WRAPPER_PART);
371            element.appendChild(element.getOwnerDocument().importNode(partValue,
372                    true));
373        }
374    
375        /**
376         * Add a jbi:part to a normalized message document
377         */
378        private static void addPart(Element parent, NodeList nodes) {
379            Element element = DomUtil.createElement(parent,
380                    JbiConstants.WSDL11_WRAPPER_PART);
381            for (int i = 0; i < nodes.getLength(); i++) {
382                Node node = nodes.item(i);
383                element.appendChild(element.getOwnerDocument().importNode(node,
384                        true));
385            }
386        }
387    
388        private static Header findHeader(List<Header> headerElement,
389                MessagePartInfo mpi) {
390            Header param = null;
391            if (headerElement != null) {
392                QName name = mpi.getConcreteName();
393                for (Header header : headerElement) {
394                    if (mpi.isElement()) {
395                        if (header.getName().getNamespaceURI() != null
396                            && header.getName().getNamespaceURI().equals(
397                                    name.getNamespaceURI())
398                            && header.getName().getLocalPart() != null
399                            && header.getName().getLocalPart().equals(
400                                    name.getLocalPart())) {
401                            param = header;
402                        }
403                    } else {
404                        if (header.getName().getLocalPart() != null
405                                && header.getName().getLocalPart().equals(
406                                    name.getLocalPart())) {
407                            param = header;
408                        }
409                    }
410                }
411            }
412            return param;
413        }
414        
415        protected boolean isRequestor(Message message) {
416            return Boolean.TRUE.equals(message.get(Message.REQUESTOR_ROLE));
417        }
418    }