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