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 }