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.soap.interceptors.jbi;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.List;
022    import java.util.Collections;
023    import java.util.Iterator;
024    
025    import javax.xml.namespace.QName;
026    import javax.xml.transform.Source;
027    import javax.xml.transform.dom.DOMSource;
028    import javax.xml.stream.XMLStreamReader;
029    import javax.xml.stream.XMLStreamException;
030    
031    import org.apache.servicemix.soap.api.Fault;
032    import org.apache.servicemix.soap.api.Message;
033    import org.apache.servicemix.soap.api.model.Operation;
034    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapMessage;
035    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapOperation;
036    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapPart;
037    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapBinding;
038    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapBinding.Style;
039    import org.apache.servicemix.soap.core.AbstractInterceptor;
040    import org.apache.servicemix.soap.util.DomUtil;
041    import org.apache.servicemix.soap.util.QNameUtil;
042    import org.apache.servicemix.soap.util.stax.StaxUtil;
043    import org.apache.servicemix.soap.util.stax.FragmentStreamReader;
044    import org.apache.servicemix.soap.util.stax.StaxSource;
045    
046    import org.w3c.dom.Document;
047    import org.w3c.dom.DocumentFragment;
048    import org.w3c.dom.Element;
049    import org.w3c.dom.Node;
050    import org.w3c.dom.NodeList;
051    
052    /**
053     * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
054     */
055    public class JbiOutWsdl1Interceptor extends AbstractInterceptor {
056    
057        private final boolean server;
058        
059        public JbiOutWsdl1Interceptor(boolean server) {
060            this.server = server;
061        }
062        
063        public void handleMessage(Message message) {
064            // Ignore faults messages
065            if (message.getContent(Exception.class) != null) {
066                return;
067            }
068            // Check if we should not use the JBI wrapper
069            if (message.get(JbiConstants.USE_JBI_WRAPPER) instanceof Boolean && ((Boolean) message.get(JbiConstants.USE_JBI_WRAPPER)) == false) {
070                return;
071            }
072            // Check if we can bypass DOM
073            Wsdl1SoapOperation wsdlOperation = getOperation(message);
074            Wsdl1SoapMessage wsdlMessage = server ? wsdlOperation.getOutput() : wsdlOperation.getInput();
075            if (wsdlOperation.getStyle() == Style.DOCUMENT
076                    && tail(wsdlMessage.getParts()).isBody()
077                    /*&& !(message.getContent(Source.class) instanceof DOMSource)*/) {
078                XMLStreamReader r = StaxUtil.createReader(message.getContent(Source.class));
079                try {
080                    r.nextTag();
081                    if (!JbiConstants.WSDL11_WRAPPER_MESSAGE.equals(r.getName())) {
082                        throw new Fault("Message wrapper element is '" + QNameUtil.toString(r.getName())
083                                + "' but expected '{" + JbiConstants.WSDL11_WRAPPER_NAMESPACE + "}message'");
084                    }
085                    for (Wsdl1SoapPart p : wsdlMessage.getParts()) {
086                        r.nextTag();
087                        if (!JbiConstants.WSDL11_WRAPPER_PART.equals(r.getName())) {
088                            throw new Fault("Unexpected part wrapper element '" + QNameUtil.toString(r.getName())
089                                    + "' expected '{" + JbiConstants.WSDL11_WRAPPER_NAMESPACE + "}part'");
090                        }
091                        r.nextTag();
092                        if (p.isBody()) {
093                            message.setContent(Source.class, StaxUtil.createSource(r));
094                        } else {
095                            Element e = StaxUtil.createElement(r);
096                            DocumentFragment frag = e.getOwnerDocument().createDocumentFragment();
097                            frag.appendChild(e.getOwnerDocument().importNode(e, true));
098                            message.getSoapHeaders().put(p.getElement(), frag);
099                            r.nextTag();
100                        }
101                    }
102                    /*
103                    r.nextTag();
104                    if (!JbiConstants.WSDL11_WRAPPER_PART.equals(r.getName())) {
105                        throw new Fault("Unexpected part wrapper element '" + QNameUtil.toString(r.getName())
106                                + "' expected '{" + JbiConstants.WSDL11_WRAPPER_NAMESPACE + "}part'");
107                    }
108                    r.nextTag();
109                    message.setContent(Source.class, new StaxSource(new FragmentStreamReader(r)));
110                    */
111                    return;
112                } catch (XMLStreamException e) {
113                    throw new Fault("Error parsing message: " + e, e);
114                }
115            }
116            // Use DOM for other requests
117            Document document = createDomMessage(message);
118            message.setContent(Source.class, new DOMSource(document));
119        }
120    
121        private <T> T tail(Collection<T> parts) {
122            T last = null;
123            for (Iterator<T> it = parts.iterator(); it.hasNext(); last = it.next());
124            return last;
125        }
126    
127        protected Document createDomMessage(Message message) {
128            Source source = message.getContent(Source.class);
129            Element element = StaxUtil.createElement(StaxUtil.createReader(source));
130            if (!JbiConstants.WSDL11_WRAPPER_NAMESPACE.equals(element.getNamespaceURI()) ||
131                !JbiConstants.WSDL11_WRAPPER_MESSAGE_LOCALNAME.equals(element.getLocalName())) {
132                throw new Fault("Message wrapper element is '" + QNameUtil.toString(element)
133                        + "' but expected '{" + JbiConstants.WSDL11_WRAPPER_NAMESPACE + "}message'");
134            }
135            List<NodeList> partsContent = new ArrayList<NodeList>();
136            Element partWrapper = DomUtil.getFirstChildElement(element);
137            while (partWrapper != null) {
138                if (!JbiConstants.WSDL11_WRAPPER_NAMESPACE.equals(element.getNamespaceURI()) ||
139                    !JbiConstants.WSDL11_WRAPPER_PART_LOCALNAME.equals(partWrapper.getLocalName())) {
140                    throw new Fault("Unexpected part wrapper element '" + QNameUtil.toString(partWrapper)
141                            + "' expected '{" + JbiConstants.WSDL11_WRAPPER_NAMESPACE + "}part'");
142                }
143                NodeList nodes = partWrapper.getChildNodes();
144                partsContent.add(nodes);
145                partWrapper = DomUtil.getNextSiblingElement(partWrapper);
146            }
147    
148            Wsdl1SoapOperation wsdlOperation = getOperation(message);
149            Wsdl1SoapMessage wsdlMessage = server ? wsdlOperation.getOutput() : wsdlOperation.getInput();
150            Collection orderedParts = wsdlMessage.getParts();
151            if (orderedParts.size() != partsContent.size()) {
152                throw new Fault("Message contains " + partsContent.size() + " part(s) but expected "
153                        + orderedParts.size() + " parts");
154            }
155    
156            Document document = DomUtil.createDocument();
157            Node body = null;
158            if (wsdlOperation.getStyle() == Style.RPC) {
159                body = DomUtil.createElement(document, wsdlMessage.getElementName());
160            }
161            int idxPart = 0;
162            for (Wsdl1SoapPart part : wsdlMessage.getParts()) {
163                NodeList nodes =  partsContent.get(idxPart++);
164                if (part.isBody()) {
165                    if (wsdlOperation.getStyle() == Style.DOCUMENT) {
166                        Element e = null;
167                        for (int i = 0; i < nodes.getLength(); i++) {
168                            Node n = nodes.item(i);
169                            if (n instanceof Element) {
170                                if (e != null) {
171                                    throw new Fault("Body part '" + part.getName() + "' contains more than one element; expected a single element.");
172                                } else {
173                                    e = (Element) n;
174                                }
175                            }
176                        }
177                        if (e == null) {
178                            throw new Fault("Body part '" + part.getName() + "' contains no element; expected a single element.");
179                        }
180                        if (!wsdlMessage.getElementName().equals(DomUtil.getQName(e))) {
181                            throw new Fault("Body part '" + part.getName() + "' element '" + DomUtil.getQName(e) + " doesn't match expected element '" + QNameUtil.toString(wsdlMessage.getElementName()) + "'");
182                        }
183                        body = document.importNode(e, true);
184                        document.appendChild(body);
185                    } else /* rpc-style */ {
186                        for (int j = 0; j < nodes.getLength(); j++) {
187                            /* note: we don't do any validation on RPC-style part value types */
188                            Element e = document.createElementNS(wsdlMessage.getElementName().getNamespaceURI(), part.getName());
189                            body.appendChild(e);
190                            e.appendChild(document.importNode(nodes.item(j), true));
191                        }
192                    }
193                } else {
194                    DocumentFragment frag = document.createDocumentFragment();
195                    Element e = null;
196                    for (int i = 0; i < nodes.getLength(); i++) {
197                        Node n = nodes.item(i);
198                        if (n instanceof Element) {
199                            if (e != null) {
200                                throw new Fault("Header part '" + part.getName() + "' contains more than one element; expected a single element.");
201                            } else {
202                                e = (Element) n;
203                            }
204                        }
205                    }
206                    if (e == null) {
207                        throw new Fault("Header part '" + part.getName() + "' contains no element; expected a single element.");
208                    }
209                    QName headerName = part.getElement();
210                    if (!headerName.equals(DomUtil.getQName(e))) {
211                        throw new Fault("Header part '" + part.getName() + "' element '" + DomUtil.getQName(e) + " doesn't match expected element '" + QNameUtil.toString(headerName) + "'");
212                    }
213                    for (int j=0; j<nodes.getLength(); j++) {
214                        frag.appendChild(document.importNode(nodes.item(j), true));
215                    }
216                    message.getSoapHeaders().put(headerName, frag);
217                }
218            }
219            return document;
220        }
221    
222        protected Wsdl1SoapOperation getOperation(Message message) {
223            Operation operation = message.get(Operation.class);
224            if (operation == null) {
225                throw new Fault("Operation not bound on this message");
226            }
227            if (operation instanceof Wsdl1SoapOperation == false) {
228                throw new Fault("Message is not bound to a WSDL 1.1 SOAP operation");
229            }
230            return (Wsdl1SoapOperation) operation;
231        }
232        
233        protected Wsdl1SoapMessage getMessage(Message message) {
234            org.apache.servicemix.soap.api.model.Message msg = message.get(org.apache.servicemix.soap.api.model.Message.class);
235            if (msg == null) {
236                throw new Fault("Message not bound on this message");
237            }
238            if (msg instanceof Wsdl1SoapMessage == false) {
239                throw new Fault("Message is not bound to a WSDL 1.1 SOAP operation message");
240            }
241            return (Wsdl1SoapMessage) msg;
242        }
243    
244    }