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 }