1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.messaging.saaj.soap.impl;
27
28import java.util.Iterator;
29import java.util.Locale;
30import java.util.logging.Level;
31
32import javax.xml.namespace.QName;
33import javax.xml.stream.XMLStreamException;
34import javax.xml.stream.XMLStreamReader;
35import javax.xml.parsers.DocumentBuilder;
36import javax.xml.parsers.DocumentBuilderFactory;
37
38import com.sun.xml.internal.messaging.saaj.util.SAAJUtil;
39
40import com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl;
41import com.sun.xml.internal.messaging.saaj.soap.SOAPDocument;
42import com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl;
43import com.sun.xml.internal.messaging.saaj.soap.StaxBridge;
44import com.sun.xml.internal.messaging.saaj.soap.name.NameImpl;
45import javax.xml.soap.Name;
46import javax.xml.soap.SOAPBody;
47import javax.xml.soap.SOAPBodyElement;
48import javax.xml.soap.SOAPElement;
49import javax.xml.soap.SOAPEnvelope;
50import javax.xml.soap.SOAPException;
51import javax.xml.soap.SOAPFault;
52import org.w3c.dom.Document;
53import org.w3c.dom.DocumentFragment;
54import org.w3c.dom.Element;
55import org.w3c.dom.Node;
56import org.w3c.dom.NodeList;
57
58/**
59 * The implementation of SOAP-ENV:BODY or the SOAPBody abstraction.
60 *
61 * @author Anil Vijendran (anil@sun.com)
62 */
63public abstract class BodyImpl extends ElementImpl implements SOAPBody {
64    private SOAPFault fault;
65//  private XMLStreamReaderToXMLStreamWriter staxBridge;
66    private StaxBridge staxBridge;
67    private boolean payloadStreamRead = false;
68
69    protected BodyImpl(SOAPDocumentImpl ownerDoc, NameImpl bodyName) {
70        super(ownerDoc, bodyName);
71    }
72
73    public BodyImpl(SOAPDocumentImpl ownerDoc, Element domElement) {
74        super(ownerDoc, domElement);
75    }
76
77    protected abstract NameImpl getFaultName(String name);
78    protected abstract boolean isFault(SOAPElement child);
79    protected abstract SOAPBodyElement createBodyElement(Name name);
80    protected abstract SOAPBodyElement createBodyElement(QName name);
81    protected abstract SOAPFault createFaultElement();
82    protected abstract QName getDefaultFaultCode();
83
84    @Override
85    public SOAPFault addFault() throws SOAPException {
86        if (hasFault()) {
87            log.severe("SAAJ0110.impl.fault.already.exists");
88            throw new SOAPExceptionImpl("Error: Fault already exists");
89        }
90
91        fault = createFaultElement();
92
93        addNode(fault);
94
95        fault.setFaultCode(getDefaultFaultCode());
96        fault.setFaultString("Fault string, and possibly fault code, not set");
97
98        return fault;
99    }
100
101    @Override
102    public SOAPFault addFault(
103        Name faultCode,
104        String faultString,
105        Locale locale)
106        throws SOAPException {
107
108        SOAPFault fault = addFault();
109        fault.setFaultCode(faultCode);
110        fault.setFaultString(faultString, locale);
111        return fault;
112    }
113
114    @Override
115   public SOAPFault addFault(
116        QName faultCode,
117        String faultString,
118        Locale locale)
119        throws SOAPException {
120
121        SOAPFault fault = addFault();
122        fault.setFaultCode(faultCode);
123        fault.setFaultString(faultString, locale);
124        return fault;
125    }
126
127    @Override
128    public SOAPFault addFault(Name faultCode, String faultString)
129        throws SOAPException {
130
131        SOAPFault fault = addFault();
132        fault.setFaultCode(faultCode);
133        fault.setFaultString(faultString);
134        return fault;
135    }
136
137    @Override
138    public SOAPFault addFault(QName faultCode, String faultString)
139        throws SOAPException {
140
141        SOAPFault fault = addFault();
142        fault.setFaultCode(faultCode);
143        fault.setFaultString(faultString);
144        return fault;
145    }
146
147    void initializeFault() {
148        FaultImpl flt = (FaultImpl) findFault();
149        fault = flt;
150    }
151
152    protected SOAPElement findFault() {
153        Iterator<Node> eachChild = getChildElementNodes();
154        while (eachChild.hasNext()) {
155            SOAPElement child = (SOAPElement) eachChild.next();
156            if (isFault(child)) {
157                return child;
158            }
159        }
160
161        return null;
162    }
163
164    @Override
165    public boolean hasFault() {
166        QName payloadQName = getPayloadQName();
167        return getFaultQName().equals(payloadQName);
168    }
169
170    private Object getFaultQName() {
171        return new QName(getNamespaceURI(), "Fault");
172    }
173
174    @Override
175    public SOAPFault getFault() {
176        if (hasFault()) {
177            if (fault == null) {
178                //initialize fault member
179                fault = (SOAPFault) getSoapDocument().find(getFirstChildElement());
180            }
181            return fault;
182        }
183        return null;
184    }
185
186    @Override
187    public SOAPBodyElement addBodyElement(Name name) throws SOAPException {
188        SOAPBodyElement newBodyElement =
189            (SOAPBodyElement) ElementFactory.createNamedElement(
190                ((SOAPDocument) getOwnerDocument()).getDocument(),
191                name.getLocalName(),
192                name.getPrefix(),
193                name.getURI());
194        if (newBodyElement == null) {
195            newBodyElement = createBodyElement(name);
196        }
197        addNode(newBodyElement);
198        return newBodyElement;
199    }
200
201    @Override
202    public SOAPBodyElement addBodyElement(QName qname) throws SOAPException {
203        SOAPBodyElement newBodyElement =
204            (SOAPBodyElement) ElementFactory.createNamedElement(
205                ((SOAPDocument) getOwnerDocument()).getDocument(),
206                qname.getLocalPart(),
207                qname.getPrefix(),
208                qname.getNamespaceURI());
209        if (newBodyElement == null) {
210            newBodyElement = createBodyElement(qname);
211        }
212        addNode(newBodyElement);
213        return newBodyElement;
214    }
215
216    @Override
217    public void setParentElement(SOAPElement element) throws SOAPException {
218
219        if (!(element instanceof SOAPEnvelope)) {
220            log.severe("SAAJ0111.impl.body.parent.must.be.envelope");
221            throw new SOAPException("Parent of SOAPBody has to be a SOAPEnvelope");
222        }
223        super.setParentElement(element);
224    }
225
226    @Override
227    protected SOAPElement addElement(Name name) throws SOAPException {
228        return addBodyElement(name);
229    }
230
231    @Override
232    protected SOAPElement addElement(QName name) throws SOAPException {
233        return addBodyElement(name);
234    }
235
236    //    public Node insertBefore(Node newElement, Node ref) throws DOMException {
237    //        if (!(newElement instanceof SOAPBodyElement) && (newElement instanceof SOAPElement)) {
238    //            newElement = new ElementWrapper((ElementImpl) newElement);
239    //        }
240    //        return super.insertBefore(newElement, ref);
241    //    }
242    //
243    //    public Node replaceChild(Node newElement, Node ref) throws DOMException {
244    //        if (!(newElement instanceof SOAPBodyElement) && (newElement instanceof SOAPElement)) {
245    //            newElement = new ElementWrapper((ElementImpl) newElement);
246    //        }
247    //        return super.replaceChild(newElement, ref);
248    //    }
249
250    @Override
251    public SOAPBodyElement addDocument(Document document)
252        throws SOAPException {
253        /*
254
255                Element rootNode =
256                    document.getDocumentElement();
257                // Causes all deferred nodes to be inflated
258                rootNode.normalize();
259                adoptElement(rootNode);
260                SOAPBodyElement bodyElement = (SOAPBodyElement) convertToSoapElement(rootNode);
261                addNode(bodyElement);
262                return bodyElement;
263        */
264        ///*
265        SOAPBodyElement newBodyElement = null;
266        DocumentFragment docFrag = document.createDocumentFragment();
267        Element rootElement = document.getDocumentElement();
268        if(rootElement != null) {
269            docFrag.appendChild(rootElement);
270
271            Document ownerDoc = getOwnerDocument();
272            // This copies the whole tree which could be very big so it's slow.
273            // However, it does have the advantage of actually working.
274            org.w3c.dom.Node replacingNode = ownerDoc.importNode(docFrag, true);
275            // Adding replacingNode at the last of the children list of body
276            addNode(replacingNode);
277            Iterator<javax.xml.soap.Node> i =
278                getChildElements(NameImpl.copyElementName(rootElement));
279            // Return the child element with the required name which is at the
280            // end of the list
281            while(i.hasNext())
282                newBodyElement = (SOAPBodyElement) i.next();
283        }
284        return newBodyElement;
285        //*/
286    }
287
288    @Override
289    protected SOAPElement convertToSoapElement(Element element) {
290        final Node soapNode = getSoapDocument().findIfPresent(element);
291        if ((soapNode instanceof SOAPBodyElement) &&
292            //this check is required because ElementImpl currently
293            // implements SOAPBodyElement
294            !(soapNode.getClass().equals(ElementImpl.class))) {
295            return (SOAPElement) soapNode;
296        } else {
297            return replaceElementWithSOAPElement(
298                element,
299                (ElementImpl) createBodyElement(NameImpl
300                    .copyElementName(element)));
301        }
302    }
303
304    @Override
305    public SOAPElement setElementQName(QName newName) throws SOAPException {
306        log.log(Level.SEVERE,
307                "SAAJ0146.impl.invalid.name.change.requested",
308                new Object[] {elementQName.getLocalPart(),
309                              newName.getLocalPart()});
310        throw new SOAPException("Cannot change name for "
311                                + elementQName.getLocalPart() + " to "
312                                + newName.getLocalPart());
313    }
314
315    @Override
316    public Document extractContentAsDocument() throws SOAPException {
317
318        Iterator<javax.xml.soap.Node> eachChild = getChildElements();
319        javax.xml.soap.Node firstBodyElement = null;
320
321        while (eachChild.hasNext() &&
322               !(firstBodyElement instanceof SOAPElement))
323            firstBodyElement = (javax.xml.soap.Node) eachChild.next();
324
325        boolean exactlyOneChildElement = true;
326        if (firstBodyElement == null)
327            exactlyOneChildElement = false;
328        else {
329            for (org.w3c.dom.Node node = firstBodyElement.getNextSibling();
330                 node != null;
331                 node = node.getNextSibling()) {
332
333                if (node instanceof Element) {
334                    exactlyOneChildElement = false;
335                    break;
336                }
337            }
338        }
339
340        if(!exactlyOneChildElement) {
341            log.log(Level.SEVERE,
342                    "SAAJ0250.impl.body.should.have.exactly.one.child");
343            throw new SOAPException("Cannot extract Document from body");
344        }
345
346        Document document = null;
347        try {
348            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", SAAJUtil.getSystemClassLoader());
349            factory.setNamespaceAware(true);
350            DocumentBuilder builder = factory.newDocumentBuilder();
351            document = builder.newDocument();
352
353            Element rootElement = (Element) document.importNode(
354                                                firstBodyElement,
355                                                true);
356
357            document.appendChild(rootElement);
358
359        } catch(Exception e) {
360            log.log(Level.SEVERE,
361                    "SAAJ0251.impl.cannot.extract.document.from.body");
362            throw new SOAPExceptionImpl(
363                "Unable to extract Document from body", e);
364        }
365
366        firstBodyElement.detachNode();
367
368        return document;
369    }
370
371    private void materializePayloadWrapException() {
372        try {
373            materializePayload();
374        } catch (SOAPException e) {
375            throw new RuntimeException(e);
376        }
377    }
378    private void materializePayload() throws SOAPException {
379        if (staxBridge != null) {
380            if (payloadStreamRead) {
381                //the payload has already been read via stream reader and the
382                //stream has been exhausted already. Throw an
383                //exception since we are now trying to materialize as DOM and
384                //there is no stream left to read
385                throw new SOAPException("SOAPBody payload stream has been fully read - cannot materialize as DOM!");
386            }
387            try {
388                staxBridge.bridgePayload();
389                staxBridge = null;
390                payloadStreamRead = true;
391            } catch (XMLStreamException e) {
392                throw new SOAPException(e);
393            }
394        }
395    }
396
397    @Override
398    public boolean hasChildNodes() {
399        boolean hasChildren = super.hasChildNodes();
400        //to answer this question we need to know _whether_ we have at least one child
401        //So no need to materialize body if we already know we have a header child
402        if (!hasChildren) {
403            materializePayloadWrapException();
404        }
405        return super.hasChildNodes();
406    }
407
408    @Override
409    public NodeList getChildNodes() {
410        materializePayloadWrapException();
411        return super.getChildNodes();
412    }
413
414    @Override
415    public Node getFirstChild() {
416        Node child = super.getFirstChild();
417        if (child == null) {
418            materializePayloadWrapException();
419        }
420        return super.getFirstChild();
421    }
422
423    public Node getFirstChildNoMaterialize() {
424        return super.getFirstChild();
425    }
426
427    @Override
428    public Node getLastChild() {
429        materializePayloadWrapException();
430        return super.getLastChild();
431    }
432
433    XMLStreamReader getPayloadReader() {
434        return staxBridge.getPayloadReader();
435    }
436
437    void setStaxBridge(StaxBridge bridge) {
438        this.staxBridge = bridge;
439    }
440
441    StaxBridge getStaxBridge() {
442        return staxBridge;
443    }
444
445    void setPayloadStreamRead() {
446        this.payloadStreamRead = true;
447    }
448
449    QName getPayloadQName() {
450        if (staxBridge != null) {
451                return staxBridge.getPayloadQName();
452        } else {
453            //not lazy - Just get first child element and return its name
454            Element elem = getFirstChildElement();
455            if (elem != null) {
456                String ns = elem.getNamespaceURI();
457                String pref = elem.getPrefix();
458                String local = elem.getLocalName();
459                if (pref != null) return new QName(ns, local, pref);
460                if (ns != null) return new QName(ns, local);
461                return new QName(local);
462            }
463        }
464        return null;
465    }
466
467    String getPayloadAttributeValue(String attName) {
468        if (staxBridge != null) {
469            return staxBridge.getPayloadAttributeValue(attName);
470        } else {
471            //not lazy -Just get first child element and return its attribute
472            Element elem = getFirstChildElement();
473            if (elem != null) {
474                return elem.getAttribute(getLocalName());
475            }
476        }
477        return null;
478    }
479
480    String getPayloadAttributeValue(QName attNAme) {
481        if (staxBridge != null) {
482            return staxBridge.getPayloadAttributeValue(attNAme);
483        } else {
484            //not lazy -Just get first child element and return its attribute
485            Element elem = getFirstChildElement();
486            if (elem != null) {
487                return elem.getAttributeNS(attNAme.getNamespaceURI(), attNAme.getLocalPart());
488            }
489        }
490        return null;
491    }
492
493    public boolean isLazy() {
494        return (staxBridge != null && !payloadStreamRead);
495    }
496
497}
498