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.marshalers;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.InputStream;
021    import java.io.SequenceInputStream;
022    import java.net.URI;
023    import java.util.Properties;
024    
025    import javax.mail.Session;
026    import javax.mail.internet.ContentType;
027    import javax.mail.internet.MimeBodyPart;
028    import javax.mail.internet.MimeMessage;
029    import javax.mail.internet.MimeMultipart;
030    import javax.xml.namespace.QName;
031    import javax.xml.parsers.DocumentBuilder;
032    import javax.xml.parsers.ParserConfigurationException;
033    import javax.xml.stream.XMLStreamConstants;
034    import javax.xml.stream.XMLStreamReader;
035    import javax.xml.transform.Source;
036    import javax.xml.transform.dom.DOMSource;
037    import javax.xml.transform.stream.StreamSource;
038    
039    import org.apache.servicemix.common.util.DOMUtil;
040    import org.apache.servicemix.jbi.jaxp.ExtendedXMLStreamReader;
041    import org.apache.servicemix.jbi.jaxp.FragmentStreamReader;
042    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
043    import org.apache.servicemix.jbi.jaxp.StaxSource;
044    import org.apache.servicemix.jbi.jaxp.StringSource;
045    import org.apache.servicemix.soap.SoapFault;
046    import org.w3c.dom.Document;
047    import org.w3c.dom.DocumentFragment;
048    import org.w3c.dom.Element;
049    import org.w3c.dom.NodeList;
050    
051    /**
052     * 
053     * @author Guillaume Nodet
054     * @version $Revision: 1.5 $
055     * @since 3.0
056     */
057    public class SoapReader {
058    
059            private SoapMarshaler marshaler;
060      
061        protected static final Source EMPTY_CONTENT = new StringSource("<payload/>");
062    
063            public SoapReader(SoapMarshaler marshaler) {
064                    this.marshaler = marshaler;
065            }
066    
067            public SoapMessage read(InputStream is, String contentType)
068                            throws Exception {
069                    if (contentType != null && startsWithCaseInsensitive(contentType, SoapMarshaler.MULTIPART_CONTENT)) {
070                            Session session = Session.getDefaultInstance(new Properties());
071                is = new SequenceInputStream(new ByteArrayInputStream(new byte[] { 13, 10 }), is);
072                            MimeMessage mime = new MimeMessage(session, is);
073                            mime.setHeader(SoapMarshaler.MIME_CONTENT_TYPE, contentType);
074                            return read(mime);
075                    } else {
076                            return read(is);
077                    }
078            }
079        
080        static boolean startsWithCaseInsensitive(String s1, String s2) {
081            return s1.regionMatches(true, 0, s2, 0, s2.length());
082        }
083    
084            public SoapMessage read(InputStream is) throws Exception {
085                    if (marshaler.isSoap()) {
086                if (marshaler.isUseDom()) {
087                    return readSoapUsingDom(is);
088                } else {
089                    return readSoapUsingStax(is);
090                }
091                    } else {
092                            SoapMessage message = new SoapMessage();
093                            message.setSource(new StreamSource(is));
094                            return message;
095                    }
096            }
097    
098        private SoapMessage readSoapUsingDom(InputStream is) throws Exception {
099            SoapMessage message = new SoapMessage();
100            DocumentBuilder builder = DOMUtil.getBuilder();
101            try {
102                Document doc = builder.parse(is);
103            message.setDocument(doc);
104            } finally {
105                DOMUtil.releaseBuilder(builder);
106            }
107            Element env = message.getDocument().getDocumentElement();
108            QName envName = DOMUtil.getQName(env);
109            if (!envName.getLocalPart().equals(SoapMarshaler.ENVELOPE)) {
110                throw new SoapFault(SoapFault.SENDER, "Unrecognized element: "
111                        + envName + ". Expecting 'Envelope'.");
112            }
113            message.setEnvelopeName(envName);
114            // Check soap 1.1 or 1.2
115            String soapUri = envName.getNamespaceURI();
116            if (!SoapMarshaler.SOAP_11_URI.equals(soapUri) && !SoapMarshaler.SOAP_12_URI.equals(soapUri)) {
117                throw new SoapFault(SoapFault.SENDER, "Unrecognized namespace: " + soapUri
118                        + " for element 'Envelope'.");
119            }
120            // Check Headers
121            Element child = DOMUtil.getFirstChildElement(env);
122            if (DOMUtil.getQName(child).equals(new QName(soapUri, SoapMarshaler.HEADER))) {
123                parseHeaders(message, child);
124                child = DOMUtil.getNextSiblingElement(child);
125            }
126            // Check Body
127            if (!DOMUtil.getQName(child).equals(new QName(soapUri, SoapMarshaler.BODY))) {
128                throw new SoapFault(SoapFault.SENDER, "Unrecognized element: "
129                        + DOMUtil.getQName(child) + ". Expecting 'Body'.");
130            }
131            // Create Source for content
132            child = DOMUtil.getFirstChildElement(child);
133            if (child != null) {
134                QName childName = DOMUtil.getQName(child);
135                message.setBodyName(childName);
136                // Check for fault
137                if (childName.equals(new QName(soapUri, SoapMarshaler.FAULT))) {
138                    message.setFault(readFaultUsingDom(child));
139                } else {
140                    message.setSource(new DOMSource(child));
141                }
142            }
143            child = DOMUtil.getNextSiblingElement(child);
144            if (child != null) {
145                throw new SoapFault(SoapFault.RECEIVER, "Body element has more than one child element.");
146            }
147            return message;
148        }
149        
150        private void parseHeaders(SoapMessage message, Element headers) {
151            for (Element child = DOMUtil.getFirstChildElement(headers);
152                 child != null;
153                 child = DOMUtil.getNextSiblingElement(child)) {
154                DocumentFragment df = child.getOwnerDocument().createDocumentFragment();
155                df.appendChild(child.cloneNode(true));
156                message.addHeader(DOMUtil.getQName(child), df);
157            }
158        }
159        
160            private SoapMessage readSoapUsingStax(InputStream is) throws Exception {
161                    SoapMessage message = new SoapMessage();
162                    XMLStreamReader reader = marshaler.getInputFactory().createXMLStreamReader(is);
163                    reader = new ExtendedXMLStreamReader(reader);
164                    reader.nextTag();
165                    // Check Envelope tag
166                    if (!reader.getLocalName().equals(SoapMarshaler.ENVELOPE)) {
167                            throw new SoapFault(SoapFault.SENDER, "Unrecognized element: "
168                                            + reader.getName() + " at ["
169                                            + reader.getLocation().getLineNumber() + ","
170                                            + reader.getLocation().getColumnNumber()
171                                            + "]. Expecting 'Envelope'.");
172                    }
173                    message.setEnvelopeName(reader.getName());
174                    // Check soap 1.1 or 1.2
175                    String soapUri = reader.getNamespaceURI();
176                    if (!SoapMarshaler.SOAP_11_URI.equals(soapUri) && !SoapMarshaler.SOAP_12_URI.equals(soapUri)) {
177                            throw new SoapFault(SoapFault.SENDER, "Unrecognized namespace: " + soapUri
178                                            + " for element 'Envelope' at ["
179                                            + reader.getLocation().getLineNumber() + ","
180                                            + reader.getLocation().getColumnNumber()
181                                            + "]. Expecting 'Envelope'.");
182                    }
183                    // Check Headers
184                    reader.nextTag();
185                    if (reader.getName().equals(new QName(soapUri, SoapMarshaler.HEADER))) {
186                            parseHeaders(message, reader);
187                            reader.nextTag();
188                    }
189                    // Check Body
190                    if (!reader.getName().equals(new QName(soapUri, SoapMarshaler.BODY))) {
191                            throw new SoapFault(SoapFault.SENDER, "Unrecognized element: "
192                                            + reader.getName() + " at ["
193                                            + reader.getLocation().getLineNumber() + ","
194                                            + reader.getLocation().getColumnNumber()
195                                            + "]. Expecting 'Body'.");
196                    }
197                    // Create Source for content
198                    if (reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
199                QName childName = reader.getName();
200                message.setBodyName(childName);
201                // Check for fault
202                if (childName.equals(new QName(soapUri, SoapMarshaler.FAULT))) {
203                    message.setFault(readFaultUsingStax(reader));
204                } else {
205                    message.setSource(new StaxSource(new FragmentStreamReader(reader)));
206                }
207                    }
208                    return message;
209            }
210        
211        private SoapFault readFaultUsingDom(Element element) throws SoapFault {
212            QName code = null;
213            QName subcode = null;
214            String reason = null;
215            URI node = null;
216            URI role = null;
217            Source details = null;
218            // Parse soap 1.1 faults
219            if (element.getNamespaceURI().equals(SoapMarshaler.SOAP_11_URI)) {
220                // Fault code
221                Element child = DOMUtil.getFirstChildElement(element);
222                checkElementName(child, SoapMarshaler.SOAP_11_FAULTCODE);
223                code = DOMUtil.createQName(child, DOMUtil.getElementText(child));
224                // Fault string
225                child = DOMUtil.getNextSiblingElement(child);
226                checkElementName(child, SoapMarshaler.SOAP_11_FAULTSTRING);
227                reason = DOMUtil.getElementText(child);
228                child = DOMUtil.getNextSiblingElement(child);
229                QName childname = DOMUtil.getQName(child);
230                // Fault actor
231                if (SoapMarshaler.SOAP_11_FAULTACTOR.equals(childname)) {
232                    node = URI.create(DOMUtil.getElementText(child));
233                    child = DOMUtil.getNextSiblingElement(child);
234                    childname = DOMUtil.getQName(child);
235                }
236                // Fault details
237                if (SoapMarshaler.SOAP_11_FAULTDETAIL.equals(childname)) {
238                    details = getDetailsAsSource(child);
239                    child = DOMUtil.getNextSiblingElement(child);
240                    childname = DOMUtil.getQName(child);
241                }
242                // Nothing should be left
243                if (childname != null) {
244                    throw new SoapFault(SoapFault.SENDER, "Unexpected element: " + childname);
245                }
246            // Parse soap 1.2 faults
247            } else {
248                // Fault code
249                Element child = DOMUtil.getFirstChildElement(element);
250                checkElementName(child, SoapMarshaler.SOAP_12_FAULTCODE);
251                Element subchild = DOMUtil.getFirstChildElement(child);
252                checkElementName(subchild, SoapMarshaler.SOAP_12_FAULTVALUE);
253                code = DOMUtil.createQName(subchild, DOMUtil.getElementText(subchild));
254                if (!SoapMarshaler.SOAP_12_CODE_DATAENCODINGUNKNOWN.equals(code) &&
255                    !SoapMarshaler.SOAP_12_CODE_MUSTUNDERSTAND.equals(code) &&
256                    !SoapMarshaler.SOAP_12_CODE_RECEIVER.equals(code) &&
257                    !SoapMarshaler.SOAP_12_CODE_SENDER.equals(code) &&
258                    !SoapMarshaler.SOAP_12_CODE_VERSIONMISMATCH.equals(code)) {
259                    throw new SoapFault(SoapFault.SENDER, "Unexpected fault code: " + code); 
260                }
261                subchild = DOMUtil.getNextSiblingElement(subchild);
262                if (subchild != null) {
263                    checkElementName(subchild, SoapMarshaler.SOAP_12_FAULTSUBCODE);
264                    Element subsubchild = DOMUtil.getFirstChildElement(subchild);
265                    checkElementName(subsubchild, SoapMarshaler.SOAP_12_FAULTVALUE);
266                    subcode = DOMUtil.createQName(subsubchild, DOMUtil.getElementText(subsubchild));
267                    subsubchild = DOMUtil.getNextSiblingElement(subsubchild);
268                    if (subsubchild != null) {
269                        checkElementName(subsubchild, SoapMarshaler.SOAP_12_FAULTSUBCODE);
270                        throw new SoapFault(SoapFault.RECEIVER, "Unsupported nested subcodes");
271                    }
272                }
273                // Fault reason
274                child = DOMUtil.getNextSiblingElement(child);
275                checkElementName(child, SoapMarshaler.SOAP_12_FAULTREASON);
276                subchild = DOMUtil.getFirstChildElement(child);
277                checkElementName(subchild, SoapMarshaler.SOAP_12_FAULTTEXT);
278                reason = DOMUtil.getElementText(subchild);
279                subchild = DOMUtil.getNextSiblingElement(subchild);
280                if (subchild != null) {
281                    throw new SoapFault(SoapFault.RECEIVER, "Unsupported multiple reasons");
282                }
283                // Fault node
284                child = DOMUtil.getNextSiblingElement(child);
285                QName childname = DOMUtil.getQName(child);
286                if (SoapMarshaler.SOAP_12_FAULTNODE.equals(childname)) {
287                    node = URI.create(DOMUtil.getElementText(child));
288                    child = DOMUtil.getNextSiblingElement(child);
289                    childname = DOMUtil.getQName(child);
290                }
291                // Fault role
292                if (SoapMarshaler.SOAP_12_FAULTROLE.equals(childname)) {
293                    role = URI.create(DOMUtil.getElementText(child));
294                    child = DOMUtil.getNextSiblingElement(child);
295                    childname = DOMUtil.getQName(child);
296                }
297                // Fault details
298                if (SoapMarshaler.SOAP_12_FAULTDETAIL.equals(childname)) {
299                    details = getDetailsAsSource(child);
300                    child = DOMUtil.getNextSiblingElement(child);
301                    childname = DOMUtil.getQName(child);
302                }
303                // Nothing should be left
304                if (childname != null) {
305                    throw new SoapFault(SoapFault.SENDER, "Unexpected element: " + childname);
306                }
307            }
308            SoapFault fault = new SoapFault(code, subcode, reason, node, role, details);
309            return fault;
310        }
311    
312        private Source getDetailsAsSource(Element parent) throws SoapFault {
313            Element main = DOMUtil.getFirstChildElement(parent);
314            Source details = null;
315            if (main != null && DOMUtil.getNextSiblingElement(main) == null) {
316                details = new DOMSource(main);
317            } else if (main != null) {
318                // Wrap nodes in a parent element
319                Document document = null;
320                try {
321                    document = new SourceTransformer().createDocument();
322                } catch (ParserConfigurationException e) {
323                    throw new SoapFault(e);
324                }
325                Element parentNode = document.createElement(SoapMarshaler.MULTIPLE_DETAILS_NODE_WRAPPER);
326                NodeList nodes = parent.getChildNodes();
327                for (int i = 0; i < nodes.getLength(); i++) {
328                    parentNode.appendChild(document.importNode(nodes.item(i), true));
329                }
330                document.appendChild(parentNode);
331                details = new DOMSource(document);
332            }
333            return details;
334        }
335        
336        private SoapFault readFaultUsingStax(XMLStreamReader reader) throws SoapFault {
337            try {
338                FragmentStreamReader rh = new FragmentStreamReader(reader);
339                Document doc = (Document) marshaler.getSourceTransformer().toDOMNode(
340                        new StaxSource(rh));
341                return readFaultUsingDom(doc.getDocumentElement());
342            } catch (SoapFault e) {
343                throw e;
344            } catch (Exception e) {
345                throw new SoapFault(e);
346            }
347        }
348        
349        private void checkElementName(Element element, QName expected) throws SoapFault {
350            QName name= DOMUtil.getQName(element);
351            if (!expected.equals(name)) {
352                throw new SoapFault(SoapFault.SENDER, "Expected element: " + expected + " but found " + name);
353            }            
354        }
355    
356            private void parseHeaders(SoapMessage message, XMLStreamReader reader)
357                            throws Exception {
358                    while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
359                            QName hn = reader.getName();
360                            FragmentStreamReader rh = new FragmentStreamReader(reader);
361                            Document doc = (Document) marshaler.getSourceTransformer().toDOMNode(
362                                            new StaxSource(rh));
363                            DocumentFragment df = doc.createDocumentFragment();
364                            df.appendChild(doc.getDocumentElement());
365                            message.addHeader(hn, df);
366                    }
367            }
368    
369            public SoapMessage read(MimeMessage mime) throws Exception {
370                    final Object content = mime.getContent();
371                    if (content instanceof MimeMultipart) {
372                            MimeMultipart multipart = (MimeMultipart) content;
373                            ContentType type = new ContentType(mime.getContentType());
374                            String contentId = type.getParameter("start");
375                            // Get request
376                            MimeBodyPart contentPart = null;
377                if (contentId != null) {
378                    contentPart = (MimeBodyPart) multipart.getBodyPart(contentId);
379                } else {
380                    for (int i = 0; i < multipart.getCount(); i++) {
381                      MimeBodyPart contentPart2 = (MimeBodyPart) multipart.getBodyPart(i);
382                      String contentType = contentPart2.getContentType();
383                      
384                      if (contentType.indexOf("xml") >= 0) {
385                        contentPart = contentPart2;
386                        break;
387                      }
388                    }
389                }
390                
391                SoapMessage message = null;
392                if (contentPart != null) {
393                  message = read(contentPart.getInputStream());  
394                } else {
395                  message = new SoapMessage();
396                  message.setSource(EMPTY_CONTENT);
397                }
398                
399                // Get attachments
400                            for (int i = 0; i < multipart.getCount(); i++) {
401                    MimeBodyPart part = (MimeBodyPart) multipart.getBodyPart(i);
402                    if (part != contentPart) {
403                        String id = part.getContentID();
404                        if (id == null) {
405                            id = "Part" + i;
406                        } else if (id.startsWith("<")) {
407                                                    id = id.substring(1, id.length() - 1);
408                                            }
409                                            message.addAttachment(id, part.getDataHandler());
410                                    }
411                            }
412                            return message;
413                    } else {
414                            throw new UnsupportedOperationException(
415                                            "Expected a javax.mail.internet.MimeMultipart object");
416                    }
417            }
418    
419    }