001    /**
002     * 
003     * Copyright 2004 Protique Ltd
004     * 
005     * Licensed under the Apache License, Version 2.0 (the "License"); 
006     * you may not use this file except in compliance with the License. 
007     * 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     **/
018    package org.codehaus.activesoap.handler;
019    
020    import javanet.staxutils.XMLStreamUtils;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    import org.codehaus.activesoap.Handler;
024    import org.codehaus.activesoap.MessageExchange;
025    import org.codehaus.activesoap.SoapFault;
026    import org.codehaus.activesoap.SoapService;
027    import org.codehaus.activesoap.HandlerRegistry;
028    import org.codehaus.activesoap.HandlerRegistry;
029    import org.codehaus.activesoap.soap.SoapVersion;
030    import org.codehaus.activesoap.util.DocumentFilterXMLStreamReader;
031    import org.codehaus.activesoap.util.DocumentFilterXMLStreamWriter;
032    import org.codehaus.activesoap.util.XMLStreamHelper;
033    
034    import javax.xml.namespace.QName;
035    import javax.xml.stream.XMLStreamConstants;
036    import javax.xml.stream.XMLStreamException;
037    import javax.xml.stream.XMLStreamReader;
038    import javax.xml.stream.XMLStreamWriter;
039    import javax.xml.stream.util.StreamReaderDelegate;
040    import java.util.Set;
041    import java.util.HashSet;
042    import java.util.Iterator;
043    
044    
045    /**
046     * A processor of a single SOAP request which is discarded after it has completed the request.
047     * This object parsers the SOAP envelope using StAX
048     * then delegates the processing of each header element and body element to a separate {@link org.codehaus.activesoap.Handler},
049     * which will typically be XMLBeans but could be anything such as SAX, TrAX, XStream, JAXB,
050     * a JAX-RPC handler, Axis, XFire etc.
051     *
052     * @version $Revision: 1.13 $
053     */
054    public class SoapRequestHandler implements Handler, XMLStreamConstants {
055    
056        private static final Log log = LogFactory.getLog(SoapRequestHandler.class);
057    
058        private SoapService service;
059        private MessageExchange exchange;
060        private Handler echoHandler = new EchoHandler();
061        private boolean wroteHeader = false;
062        private boolean strict = true;
063        private Set handlersUsed;
064    
065        // local caches
066        private SoapVersion soap;
067        private String prefix;
068    
069        public SoapRequestHandler(SoapService service, SoapVersion soap) {
070            this.service = service;
071            this.soap = soap;
072            this.prefix = soap.getPrefix();
073        }
074    
075        public void invoke(MessageExchange exchange) throws Exception {
076            XMLStreamReader in = exchange.getIn();
077            XMLStreamWriter out = exchange.getOut();
078            this.exchange = exchange;
079            wroteHeader = false;
080            try {
081                skipToElementStart("Envelope");
082                String namespace = in.getNamespaceURI();
083                if (out != null) {
084                    out.writeStartDocument();
085                    out.setPrefix(prefix, soap.getNamespace());
086                    soap.writeStartElement(out, "Envelope");
087                    if (!service.isRepairingNamespace()) {
088                        out.writeNamespace(prefix, soap.getNamespace());
089                        XMLStreamHelper.writeNamespacesExcludingPrefixAndNamespace(out, in, prefix, namespace);
090                    }
091                }
092                SoapFault fault = validateEnvelope(namespace);
093                in.next();
094    
095                // header processing
096                if (fault == null) {
097                    try {
098                        if (skipToHeader()) {
099                            processHeaders(out);
100                        }
101                    }
102                    catch (SoapFault e) {
103                        fault = e;
104                    }
105                    catch (Exception e) {
106                        log.warn("Caught: " + e, e);
107                        fault = new SoapFault(e);
108                    }
109                    if (out != null) {
110                        if (wroteHeader) {
111                            out.writeEndElement();
112                        }
113                    }
114                }
115    
116                if (out != null) {
117                    soap.writeStartElement(out, "Body");
118                }
119    
120                if (fault != null) {
121                    writeFault(out, fault);
122                }
123                else {
124                    try {
125                        skipToStartBodyElement();
126                        processBody(out);
127                    }
128                    catch (SoapFault e) {
129                        writeFault(out, e);
130                    }
131                    catch (Exception e) {
132                        handleException(out, e);
133                    }
134                }
135                if (out != null) {
136                    out.writeEndElement();
137                    out.writeEndElement();
138                    out.writeEndDocument();
139                }
140    
141                out = close(out);
142            }
143            finally {
144                ensureClosed(out);
145            }
146        }
147    
148        // Properties
149        //-------------------------------------------------------------------------
150        public boolean isStrict() {
151            return strict;
152        }
153    
154        public void setStrict(boolean strict) {
155            this.strict = strict;
156        }
157    
158        public Set getHandlersUsed() {
159            if (handlersUsed == null) {
160                handlersUsed = new HashSet();
161            }
162            return handlersUsed;
163        }
164    
165        // Implementation methods
166        //-------------------------------------------------------------------------
167        protected void processHeaders(XMLStreamWriter out) throws Exception {
168            XMLStreamReader in = exchange.getIn();
169            QName headerName = in.getName();
170    
171            int headerCount = 1;
172            int notUnderstood = 0;
173            String notUnderstoodRole = null;
174    
175            while (true) {
176                in.next();
177                if (in.isEndElement()) {
178                    QName name = in.getName();
179                    if (name.equals(headerName)) {
180                        if (--headerCount <= 0) {
181                            in.next();
182                            break;
183                        }
184                    }
185                }
186                else if (in.isStartElement()) {
187                    QName name = in.getName();
188                    if (name.equals(headerName)) {
189                        headerCount++;
190                    }
191                    else {
192                        HandlerRegistry handlerRegistry = service.getHandlerRegistry();
193    
194                        String namespace = soap.getNamespace();
195                        boolean mustUnderstand = isTrue(headerName.getPrefix(), in.getAttributeValue(namespace, "mustUnderstand"));
196                        String role = in.getAttributeValue(namespace, "role");
197                        Handler handler = handlerRegistry.getHandler(name);
198                        if (handler instanceof HandlerLifecycle) {
199                            getHandlersUsed().add(handler);
200                        }
201                        boolean hasRole = hasRole(role);
202                        boolean ultimateReceiver = isUltimateReceiver(role);
203                        boolean intermediary = service.isIntermediary();
204                        boolean nextRole = isNextRole(role);
205                        if (mustUnderstand && handler == null && (!intermediary || (intermediary && hasRole))) {
206                            // lets output a not understand header
207                            checkHeaderElementWritten(out);
208                            soap.writeStartElement(out, "NotUnderstood");
209                            if (!service.isRepairingNamespace()) {
210                                XMLStreamHelper.writeNamespaces(out, in, soap.getPrefix());
211                            }
212                            out.writeAttribute("qname", getNameText(name));
213                            out.writeEndElement();
214    
215                            notUnderstood++;
216                            if (notUnderstoodRole == null && role != null && !ultimateReceiver && !nextRole && intermediary) {
217                                notUnderstoodRole = role;
218                            }
219    
220                            XMLStreamUtils.skipElement(in);
221                        }
222                        else {
223                            ultimateReceiver = ultimateReceiver || nextRole;
224                            if (handler != null) {
225                                if (notUnderstood > 0 || (!hasRole && role != null && !ultimateReceiver)) {
226                                    // don't process headers once at least one is not understood as we'll
227                                    // generate a fault as soon as the header processing is complete
228                                    XMLStreamUtils.skipElement(in);
229                                }
230                                else {
231                                    checkHeaderElementWritten(out);
232                                    handler.invoke(exchange.newInstance(new DocumentFilterXMLStreamReader(name, in), out));
233                                }
234                            }
235                            else {
236    
237                                // we need to decide whether to forward the header on
238                                // if we are an intermediary
239                                boolean echo = (intermediary && !hasRole);
240                                if (out != null && notUnderstood == 0 && echo) {
241                                    checkHeaderElementWritten(out);
242                                    echoHandler.invoke(exchange.newInstance(new DocumentFilterXMLStreamReader(name, in), out));
243                                }
244                                else {
245                                    XMLStreamUtils.skipElement(in);
246                                }
247                            }
248                        }
249                    }
250                }
251            }
252            if (notUnderstood > 0) {
253                throw new SoapFault("MustUnderstand", "Header not understood", notUnderstoodRole, notUnderstoodRole);
254            }
255        }
256    
257        protected boolean hasRole(String role) {
258            boolean answer = false;
259            if (role != null) {
260                return service.hasRole(role);
261            }
262            return answer;
263        }
264    
265        protected boolean isNextRole(String role) {
266            boolean answer = false;
267            if (role != null) {
268                return role.equals(soap.getNextRole());
269            }
270            return answer;
271        }
272    
273        protected boolean isUltimateReceiver(String role) {
274            boolean answer = false;
275            if (role != null) {
276                return role.equals(soap.getUltimateReceiverRole());
277            }
278            return answer;
279        }
280    
281        protected SoapFault validateEnvelope(String namespace) {
282            if (!soap.getNamespace().equals(namespace)) {
283                return new SoapFault("VersionMismatch", " Wrong Version ");
284            }
285    
286            if (strict) {
287                XMLStreamReader in = exchange.getIn();
288                int count = in.getAttributeCount();
289                for (int i = 0; i < count; i++) {
290                    String ns = in.getAttributeNamespace(i);
291                    // lets check for non-namespaced attribute
292                    if (ns == null || ns.length() == 0) {
293                        return new SoapFault("Sender", "A SOAP 1.2 Envelope element cannot have non Namespace qualified\n" +
294                                "          attributes");
295                    }
296                    else if (ns.equals(namespace)) {
297                        // cannot specify encoding style on envelope
298                        String localName = in.getAttributeLocalName(i);
299                        if (localName.equals("encodingStyle")) {
300                            return new SoapFault("Sender", prefix + ":encodingStyle cannot be specified on the " + prefix + ":Envelope");
301                        }
302                    }
303                }
304            }
305            return null;
306        }
307    
308        protected void checkHeaderElementWritten(XMLStreamWriter out) throws XMLStreamException {
309            if (!wroteHeader) {
310                wroteHeader = true;
311                soap.writeStartElement(out, "Header");
312            }
313        }
314    
315        protected void processBody(XMLStreamWriter out) throws Exception {
316            XMLStreamReader in = exchange.getIn();
317            // lets skip pass the SOAP Body element
318            XMLStreamHelper.skipToStartOfElement(in);
319            in.next();
320    
321            while (XMLStreamHelper.skipToStartOfElement(in)) {
322                DocumentFilterXMLStreamWriter bodyOutStream = new DocumentFilterXMLStreamWriter(out);
323                QName name = in.getName();
324                validateEncodingStyle(in.getAttributeValue(soap.getNamespace(), "encodingStyle"));
325                StreamReaderDelegate filterIn = new DocumentFilterXMLStreamReader(name, in);
326                Handler bodyHandler = service.getHandlerRegistry().getBodyHandler();
327                if (bodyHandler instanceof HandlerLifecycle) {
328                    getHandlersUsed().add(bodyHandler);
329                }
330                bodyHandler.invoke(exchange.newInstance(filterIn, bodyOutStream));
331                in.next();
332            }
333    
334            fireOnComplete();
335        }
336    
337        /**
338         * lets notify the lifecycles of interested handlers
339         *
340         * @throws Exception
341         */
342        protected void fireOnComplete() throws Exception {
343            if (handlersUsed != null) {
344                for (Iterator iter = handlersUsed.iterator(); iter.hasNext();) {
345                    HandlerLifecycle lifecycle = (HandlerLifecycle) iter.next();
346                    lifecycle.onComplete(exchange);
347                }
348            }
349        }
350    
351        /**
352         * Throws a {@link SoapFault} if the encoding style is not supported
353         */
354        protected void validateEncodingStyle(String encodingStyle) throws SoapFault {
355            if (encodingStyle != null && !service.getEncodingStyles().contains(encodingStyle)) {
356                throw new SoapFault("DataEncodingUnknown", "Unknown Data Encoding Style");
357            }
358        }
359    
360        protected void writeFault(XMLStreamWriter out, SoapFault fault) throws XMLStreamException {
361            if (out != null) {
362                soap.writeFault(out, fault);
363            }
364    
365        }
366    
367        protected void handleException(XMLStreamWriter out, Exception e) throws XMLStreamException {
368            log.warn("Failed: " + e, e);
369            writeFault(out, new SoapFault(e));
370        }
371    
372        /**
373         * Returns the Qname as a name, typically as 'prefix:localPart' unless
374         * there is no prefix in which case just the 'localPart' is returned.
375         */
376        protected String getNameText(QName name) {
377            String prefix = name.getPrefix();
378            if (prefix != null && prefix.length() > 0) {
379                return prefix + ":" + name.getLocalPart();
380            }
381            return name.getLocalPart();
382        }
383    
384    
385        protected void ensureClosed(XMLStreamWriter out) {
386            try {
387                close(out);
388            }
389            catch (XMLStreamException e) {
390                // ignore exception since we're already about to throw
391                // log.warn("Failed to close XMLStreamReader: " + e, e);
392            }
393            if (out != null) {
394                try {
395                    out.close();
396                }
397                catch (XMLStreamException e) {
398                    // ignore exception since we're already about to throw
399                }
400            }
401        }
402    
403        protected XMLStreamWriter close(XMLStreamWriter out) throws XMLStreamException {
404            XMLStreamReader in = exchange.getIn();
405            if (in != null) {
406                // lets only try close once
407                XMLStreamReader old = in;
408                in = null;
409                old.close();
410            }
411            if (out != null) {
412                out.close();
413            }
414            return null;
415        }
416    
417        /**
418         * Return true if the attribute is not null and contains 'true'
419         */
420        protected boolean isTrue(String prefix, String value) throws SoapFault {
421            if (value != null) {
422                if (value.equals("true") || value.equals("1")) {
423                    return true;
424                }
425                else if (value.equals("false") || value.equals("0")) {
426                    return false;
427                }
428                else {
429                    throw new SoapFault("Sender", prefix + ":mustUnderstand is a xsd:boolean");
430                }
431            }
432            return false;
433        }
434    
435        protected boolean skipToHeader() throws XMLStreamException {
436            XMLStreamReader in = exchange.getIn();
437            skipToStartElement("Header");
438    
439            String actual = in.getLocalName();
440            if (actual.equals("Header")) {
441                return true;
442            }
443            else if (actual.equals("Body")) {
444                return false;
445            }
446            else {
447                throw new XMLStreamException("Expected element 'Header' or 'Body' but got '" + actual + "' at: " + in.getLocation());
448            }
449        }
450    
451        protected void skipToStartBodyElement() throws XMLStreamException, SoapFault {
452            XMLStreamReader in = exchange.getIn();
453            if (!in.isStartElement()) {
454                while (in.hasNext()) {
455                    in.next();
456                    if (in.isStartElement()) {
457                        break;
458                    }
459                }
460                if (!in.isStartElement()) {
461                    throw new SoapFault("Sender", prefix + ":Body must be present in a SOAP 1.2 envelope");
462                }
463            }
464            if (!soap.getBody().equals(in.getName())) {
465                throw new SoapFault("Sender", prefix + ":Body must be present in a SOAP 1.2 envelope");
466            }
467        }
468    
469    
470        protected void skipToElementStart(String expected) throws XMLStreamException {
471            XMLStreamReader in = exchange.getIn();
472            skipToStartElement(expected);
473            String actual = in.getLocalName();
474            if (!expected.equals(actual)) {
475                throw new XMLStreamException("Expected element '" + expected + "' but got '" + actual + "' at: " + in.getLocation());
476            }
477        }
478    
479        protected void skipToStartElement(String expected) throws XMLStreamException {
480            XMLStreamReader in = exchange.getIn();
481            if (!in.isStartElement()) {
482                while (in.hasNext()) {
483                    in.next();
484                    if (in.isStartElement()) {
485                        break;
486                    }
487                }
488                if (!in.isStartElement()) {
489                    throw new XMLStreamException("Expected element '" + expected + " but document finished");
490                }
491            }
492        }
493    
494    
495        protected void skipToEndOfTag(QName name) throws XMLStreamException {
496            XMLStreamReader in = exchange.getIn();
497            while (in.hasNext()) {
498                in.next();
499    
500                if (in.isEndElement()) {
501                    QName actual = in.getName();
502                    if (actual != null && actual.equals(name)) {
503                        return;
504                    }
505                }
506            }
507            throw new XMLStreamException("Expected element '" + name + " but document finished");
508        }
509    
510    }