1 /* 2 * Copyright (c) 1997, 2010, 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 26 package com.sun.xml.internal.messaging.saaj.soap; 27 28 import java.io.*; 29 import java.util.*; 30 import java.util.logging.Level; 31 import java.util.logging.Logger; 32 33 import javax.activation.DataHandler; 34 import javax.activation.DataSource; 35 import javax.xml.soap.*; 36 import javax.xml.transform.Source; 37 import javax.xml.transform.stream.StreamSource; 38 39 import com.sun.xml.internal.messaging.saaj.packaging.mime.Header; 40 import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.*; 41 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.*; 42 import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException; 43 44 import com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl; 45 import com.sun.xml.internal.messaging.saaj.soap.impl.EnvelopeImpl; 46 import com.sun.xml.internal.messaging.saaj.util.*; 47 import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart; 48 49 /** 50 * The message implementation for SOAP messages with 51 * attachments. Messages for specific profiles will likely extend this 52 * MessageImpl class and add more value for that particular profile. 53 * 54 * @author Anil Vijendran (akv@eng.sun.com) 55 * @author Rajiv Mordani (rajiv.mordani@sun.com) 56 * @author Manveen Kaur (manveen.kaur@sun.com) 57 */ 58 59 public abstract class MessageImpl 60 extends SOAPMessage 61 implements SOAPConstants { 62 63 64 public static final String CONTENT_ID = "Content-ID"; 65 public static final String CONTENT_LOCATION = "Content-Location"; 66 67 protected static final Logger log = 68 Logger.getLogger(LogDomainConstants.SOAP_DOMAIN, 69 "com.sun.xml.internal.messaging.saaj.soap.LocalStrings"); 70 71 protected static final int PLAIN_XML_FLAG = 1; // 00001 72 protected static final int MIME_MULTIPART_FLAG = 2; // 00010 73 protected static final int SOAP1_1_FLAG = 4; // 00100 74 protected static final int SOAP1_2_FLAG = 8; // 01000 75 //protected static final int MIME_MULTIPART_XOP_FLAG = 14; // 01110 76 protected static final int MIME_MULTIPART_XOP_SOAP1_1_FLAG = 6; // 00110 77 protected static final int MIME_MULTIPART_XOP_SOAP1_2_FLAG = 10; // 01010 78 protected static final int XOP_FLAG = 13; // 01101 79 protected static final int FI_ENCODED_FLAG = 16; // 10000 80 81 protected MimeHeaders headers; 82 protected ContentType contentType; 83 protected SOAPPartImpl soapPartImpl; 84 protected FinalArrayList attachments; 85 protected boolean saved = false; 86 protected byte[] messageBytes; 87 protected int messageByteCount; 88 protected HashMap properties = new HashMap(); 89 90 // used for lazy attachment initialization 91 protected MimeMultipart multiPart = null; 92 protected boolean attachmentsInitialized = false; 93 94 /** 95 * True if this part is encoded using Fast Infoset. 96 * MIME -> application/fastinfoset 97 */ 98 protected boolean isFastInfoset = false; 99 100 /** 101 * True if the Accept header of this message includes 102 * application/fastinfoset 103 */ 104 protected boolean acceptFastInfoset = false; 105 106 protected MimeMultipart mmp = null; 107 108 // if attachments are present, don't read the entire message in byte stream in saveTo() 109 private boolean optimizeAttachmentProcessing = true; 110 111 private InputStream inputStreamAfterSaveChanges = null; 112 113 // switch back to old MimeMultipart incase of problem 114 private static boolean switchOffBM = false; 115 private static boolean switchOffLazyAttachment = false; 116 private static boolean useMimePull = false; 117 118 static { 119 String s = SAAJUtil.getSystemProperty("saaj.mime.optimization"); 120 if ((s != null) && s.equals("false")) { 121 switchOffBM = true; 122 } 123 s = SAAJUtil.getSystemProperty("saaj.lazy.mime.optimization"); 124 if ((s != null) && s.equals("false")) { 125 switchOffLazyAttachment = true; 126 } 127 useMimePull = SAAJUtil.getSystemBoolean("saaj.use.mimepull"); 128 129 } 130 131 //property to indicate optimized serialization for lazy attachments 132 private boolean lazyAttachments = false; 133 134 // most of the times, Content-Types are already all lower cased. 135 // String.toLowerCase() works faster in this case, so even if you 136 // are only doing one comparison, it pays off to use String.toLowerCase() 137 // than String.equalsIgnoreCase(). When you do more than one comparison, 138 // the benefits of String.toLowerCase() dominates. 139 // 140 // 141 // for FI, 142 // use application/fastinfoset for SOAP 1.1 143 // use application/soap+fastinfoset for SOAP 1.2 144 // to speed up comparisons, test methods always use lower cases. 145 146 /** 147 * @param primary 148 * must be all lower case 149 * @param sub 150 * must be all lower case 151 */ 152 private static boolean isSoap1_1Type(String primary, String sub) { 153 return primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml") 154 || primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml-soap") 155 || primary.equals("application") 156 && sub.equals("fastinfoset"); 157 } 158 159 /** 160 * @param type 161 * must be all lower case 162 */ 163 private static boolean isEqualToSoap1_1Type(String type) { 164 return type.startsWith("text/xml") || 165 type.startsWith("application/fastinfoset"); 166 } 167 168 /** 169 * @param primary 170 * must be all lower case 171 * @param sub 172 * must be all lower case 173 */ 174 private static boolean isSoap1_2Type(String primary, String sub) { 175 return primary.equals("application") 176 && (sub.equals("soap+xml") 177 || sub.equals("soap+fastinfoset")); 178 } 179 180 /** 181 * @param type 182 * must be all lower case 183 */ 184 private static boolean isEqualToSoap1_2Type(String type) { 185 return type.startsWith("application/soap+xml") || 186 type.startsWith("application/soap+fastinfoset"); 187 } 188 189 /** 190 * Construct a new message. This will be invoked before message 191 * sends. 192 */ 193 protected MessageImpl() { 194 this(false, false); 195 attachmentsInitialized = true; 196 } 197 198 /** 199 * Construct a new message. This will be invoked before message 200 * sends. 201 */ 202 protected MessageImpl(boolean isFastInfoset, boolean acceptFastInfoset) { 203 this.isFastInfoset = isFastInfoset; 204 this.acceptFastInfoset = acceptFastInfoset; 205 206 headers = new MimeHeaders(); 207 headers.setHeader("Accept", getExpectedAcceptHeader()); 208 contentType = new ContentType(); 209 } 210 211 /** 212 * Shallow copy. 213 */ 214 protected MessageImpl(SOAPMessage msg) { 215 if (!(msg instanceof MessageImpl)) { 216 // don't know how to handle this. 217 } 218 MessageImpl src = (MessageImpl) msg; 219 this.headers = src.headers; 220 this.soapPartImpl = src.soapPartImpl; 221 this.attachments = src.attachments; 222 this.saved = src.saved; 223 this.messageBytes = src.messageBytes; 224 this.messageByteCount = src.messageByteCount; 225 this.properties = src.properties; 226 this.contentType = src.contentType; 227 } 228 229 /** 230 * @param stat 231 * the mask value obtained from {@link #identifyContentType(ContentType)} 232 */ 233 protected static boolean isSoap1_1Content(int stat) { 234 return (stat & SOAP1_1_FLAG) != 0; 235 } 236 237 /** 238 * @param stat 239 * the mask value obtained from {@link #identifyContentType(ContentType)} 240 */ 241 protected static boolean isSoap1_2Content(int stat) { 242 return (stat & SOAP1_2_FLAG) != 0; 243 } 244 245 private static boolean isMimeMultipartXOPSoap1_2Package(ContentType contentType) { 246 String type = contentType.getParameter("type"); 247 if (type == null) { 248 return false; 249 } 250 type = type.toLowerCase(); 251 if (!type.startsWith("application/xop+xml")) { 252 return false; 253 } 254 String startinfo = contentType.getParameter("start-info"); 255 if (startinfo == null) { 256 return false; 257 } 258 startinfo = startinfo.toLowerCase(); 259 return isEqualToSoap1_2Type(startinfo); 260 } 261 262 263 //private static boolean isMimeMultipartXOPPackage(ContentType contentType) { 264 private static boolean isMimeMultipartXOPSoap1_1Package(ContentType contentType) { 265 String type = contentType.getParameter("type"); 266 if(type==null) 267 return false; 268 269 type = type.toLowerCase(); 270 if(!type.startsWith("application/xop+xml")) 271 return false; 272 273 String startinfo = contentType.getParameter("start-info"); 274 if(startinfo == null) 275 return false; 276 startinfo = startinfo.toLowerCase(); 277 return isEqualToSoap1_1Type(startinfo); 278 } 279 280 private static boolean isSOAPBodyXOPPackage(ContentType contentType){ 281 String primary = contentType.getPrimaryType(); 282 String sub = contentType.getSubType(); 283 284 if (primary.equalsIgnoreCase("application")) { 285 if (sub.equalsIgnoreCase("xop+xml")) { 286 String type = getTypeParameter(contentType); 287 return isEqualToSoap1_2Type(type) || isEqualToSoap1_1Type(type); 288 } 289 } 290 return false; 291 } 292 293 /** 294 * Construct a message from an input stream. When messages are 295 * received, there's two parts -- the transport headers and the 296 * message content in a transport specific stream. 297 */ 298 protected MessageImpl(MimeHeaders headers, final InputStream in) 299 throws SOAPExceptionImpl { 300 contentType = parseContentType(headers); 301 init(headers,identifyContentType(contentType),contentType,in); 302 } 303 304 private static ContentType parseContentType(MimeHeaders headers) throws SOAPExceptionImpl { 305 final String ct; 306 if (headers != null) 307 ct = getContentType(headers); 308 else { 309 log.severe("SAAJ0550.soap.null.headers"); 310 throw new SOAPExceptionImpl("Cannot create message: " + 311 "Headers can't be null"); 312 } 313 314 if (ct == null) { 315 log.severe("SAAJ0532.soap.no.Content-Type"); 316 throw new SOAPExceptionImpl("Absent Content-Type"); 317 } 318 try { 319 return new ContentType(ct); 320 } catch (Throwable ex) { 321 log.severe("SAAJ0535.soap.cannot.internalize.message"); 322 throw new SOAPExceptionImpl("Unable to internalize message", ex); 323 } 324 } 325 326 /** 327 * Construct a message from an input stream. When messages are 328 * received, there's two parts -- the transport headers and the 329 * message content in a transport specific stream. 330 * 331 * @param contentType 332 * The parsed content type header from the headers variable. 333 * This is redundant parameter, but it avoids reparsing this header again. 334 * @param stat 335 * The result of {@link #identifyContentType(ContentType)} over 336 * the contentType parameter. This redundant parameter, but it avoids 337 * recomputing this information again. 338 */ 339 protected MessageImpl(MimeHeaders headers, final ContentType contentType, int stat, final InputStream in) throws SOAPExceptionImpl { 340 init(headers, stat, contentType, in); 341 342 } 343 344 private void init(MimeHeaders headers, int stat, final ContentType contentType, final InputStream in) throws SOAPExceptionImpl { 345 this.headers = headers; 346 347 try { 348 349 // Set isFastInfoset/acceptFastInfoset flag based on MIME type 350 if ((stat & FI_ENCODED_FLAG) > 0) { 351 isFastInfoset = acceptFastInfoset = true; 352 } 353 354 // If necessary, inspect Accept header to set acceptFastInfoset 355 if (!isFastInfoset) { 356 String[] values = headers.getHeader("Accept"); 357 if (values != null) { 358 for (int i = 0; i < values.length; i++) { 359 StringTokenizer st = new StringTokenizer(values[i], ","); 360 while (st.hasMoreTokens()) { 361 final String token = st.nextToken().trim(); 362 if (token.equalsIgnoreCase("application/fastinfoset") || 363 token.equalsIgnoreCase("application/soap+fastinfoset")) { 364 acceptFastInfoset = true; 365 break; 366 } 367 } 368 } 369 } 370 } 371 372 if (!isCorrectSoapVersion(stat)) { 373 log.log( 374 Level.SEVERE, 375 "SAAJ0533.soap.incorrect.Content-Type", 376 new String[] { 377 contentType.toString(), 378 getExpectedContentType()}); 379 throw new SOAPVersionMismatchException( 380 "Cannot create message: incorrect content-type for SOAP version. Got: " 381 + contentType 382 + " Expected: " 383 + getExpectedContentType()); 384 } 385 386 if ((stat & PLAIN_XML_FLAG) != 0) { 387 if (isFastInfoset) { 388 getSOAPPart().setContent( 389 FastInfosetReflection.FastInfosetSource_new(in)); 390 } else { 391 initCharsetProperty(contentType); 392 getSOAPPart().setContent(new StreamSource(in)); 393 } 394 } 395 else if ((stat & MIME_MULTIPART_FLAG) != 0) { 396 DataSource ds = new DataSource() { 397 public InputStream getInputStream() { 398 return in; 399 } 400 401 public OutputStream getOutputStream() { 402 return null; 403 } 404 405 public String getContentType() { 406 return contentType.toString(); 407 } 408 409 public String getName() { 410 return ""; 411 } 412 }; 413 414 multiPart = null; 415 if (useMimePull) { 416 multiPart = new MimePullMultipart(ds,contentType); 417 } else if (switchOffBM) { 418 multiPart = new MimeMultipart(ds,contentType); 419 } else { 420 multiPart = new BMMimeMultipart(ds,contentType); 421 } 422 423 String startParam = contentType.getParameter("start"); 424 MimeBodyPart soapMessagePart = null; 425 InputStream soapPartInputStream = null; 426 String contentID = null; 427 String contentIDNoAngle = null; 428 if (switchOffBM || switchOffLazyAttachment) { 429 if(startParam == null) { 430 soapMessagePart = multiPart.getBodyPart(0); 431 for (int i = 1; i < multiPart.getCount(); i++) { 432 initializeAttachment(multiPart, i); 433 } 434 } else { 435 soapMessagePart = multiPart.getBodyPart(startParam); 436 for (int i = 0; i < multiPart.getCount(); i++) { 437 contentID = multiPart.getBodyPart(i).getContentID(); 438 // Old versions of AXIS2 put angle brackets around the content 439 // id but not the start param 440 contentIDNoAngle = (contentID != null) ? 441 contentID.replaceFirst("^<", "").replaceFirst(">$", "") : null; 442 if(!startParam.equals(contentID) && !startParam.equals(contentIDNoAngle)) 443 initializeAttachment(multiPart, i); 444 } 445 } 446 } else { 447 if (useMimePull) { 448 MimePullMultipart mpMultipart = (MimePullMultipart)multiPart; 449 MIMEPart sp = mpMultipart.readAndReturnSOAPPart(); 450 soapMessagePart = new MimeBodyPart(sp); 451 soapPartInputStream = sp.readOnce(); 452 } else { 453 BMMimeMultipart bmMultipart = 454 (BMMimeMultipart) multiPart; 455 InputStream stream = bmMultipart.initStream(); 456 457 SharedInputStream sin = null; 458 if (stream instanceof SharedInputStream) { 459 sin = (SharedInputStream) stream; 460 } 461 462 String boundary = "--" + 463 contentType.getParameter("boundary"); 464 byte[] bndbytes = ASCIIUtility.getBytes(boundary); 465 if (startParam == null) { 466 soapMessagePart = 467 bmMultipart.getNextPart(stream, bndbytes, sin); 468 bmMultipart.removeBodyPart(soapMessagePart); 469 } else { 470 MimeBodyPart bp = null; 471 try { 472 while (!startParam.equals(contentID) && !startParam.equals(contentIDNoAngle)) { 473 bp = bmMultipart.getNextPart( 474 stream, bndbytes, sin); 475 contentID = bp.getContentID(); 476 // Old versions of AXIS2 put angle brackets around the content 477 // id but not the start param 478 contentIDNoAngle = (contentID != null) ? 479 contentID.replaceFirst("^<", "").replaceFirst(">$", "") : null; 480 } 481 soapMessagePart = bp; 482 bmMultipart.removeBodyPart(bp); 483 } catch (Exception e) { 484 throw new SOAPExceptionImpl(e); 485 } 486 } 487 } 488 } 489 490 if (soapPartInputStream == null && soapMessagePart != null) { 491 soapPartInputStream = soapMessagePart.getInputStream(); 492 } 493 494 ContentType soapPartCType = new ContentType( 495 soapMessagePart.getContentType()); 496 initCharsetProperty(soapPartCType); 497 String baseType = soapPartCType.getBaseType().toLowerCase(); 498 if(!(isEqualToSoap1_1Type(baseType) 499 || isEqualToSoap1_2Type(baseType) 500 || isSOAPBodyXOPPackage(soapPartCType))) { 501 log.log(Level.SEVERE, 502 "SAAJ0549.soap.part.invalid.Content-Type", 503 new Object[] {baseType}); 504 throw new SOAPExceptionImpl( 505 "Bad Content-Type for SOAP Part : " + 506 baseType); 507 } 508 509 SOAPPart soapPart = getSOAPPart(); 510 setMimeHeaders(soapPart, soapMessagePart); 511 soapPart.setContent(isFastInfoset ? 512 (Source) FastInfosetReflection.FastInfosetSource_new( 513 soapPartInputStream) : 514 (Source) new StreamSource(soapPartInputStream)); 515 } else { 516 log.severe("SAAJ0534.soap.unknown.Content-Type"); 517 throw new SOAPExceptionImpl("Unrecognized Content-Type"); 518 } 519 } catch (Throwable ex) { 520 log.severe("SAAJ0535.soap.cannot.internalize.message"); 521 throw new SOAPExceptionImpl("Unable to internalize message", ex); 522 } 523 needsSave(); 524 } 525 526 public boolean isFastInfoset() { 527 return isFastInfoset; 528 } 529 530 public boolean acceptFastInfoset() { 531 return acceptFastInfoset; 532 } 533 534 public void setIsFastInfoset(boolean value) { 535 if (value != isFastInfoset) { 536 isFastInfoset = value; 537 if (isFastInfoset) { 538 acceptFastInfoset = true; 539 } 540 saved = false; // ensure transcoding if necessary 541 } 542 } 543 544 public Object getProperty(String property) { 545 return (String) properties.get(property); 546 } 547 548 public void setProperty(String property, Object value) { 549 verify(property, value); 550 properties.put(property, value); 551 } 552 553 private void verify(String property, Object value) { 554 if (property.equalsIgnoreCase(SOAPMessage.WRITE_XML_DECLARATION)) { 555 if (!("true".equals(value) || "false".equals(value))) 556 throw new RuntimeException( 557 property + " must have value false or true"); 558 559 try { 560 EnvelopeImpl env = (EnvelopeImpl) getSOAPPart().getEnvelope(); 561 if ("true".equalsIgnoreCase((String)value)) { 562 env.setOmitXmlDecl("no"); 563 } else if ("false".equalsIgnoreCase((String)value)) { 564 env.setOmitXmlDecl("yes"); 565 } 566 } catch (Exception e) { 567 log.log(Level.SEVERE, "SAAJ0591.soap.exception.in.set.property", 568 new Object[] {e.getMessage(), "javax.xml.soap.write-xml-declaration"}); 569 throw new RuntimeException(e); 570 } 571 return; 572 } 573 574 if (property.equalsIgnoreCase(SOAPMessage.CHARACTER_SET_ENCODING)) { 575 try { 576 ((EnvelopeImpl) getSOAPPart().getEnvelope()).setCharsetEncoding((String)value); 577 } catch (Exception e) { 578 log.log(Level.SEVERE, "SAAJ0591.soap.exception.in.set.property", 579 new Object[] {e.getMessage(), "javax.xml.soap.character-set-encoding"}); 580 throw new RuntimeException(e); 581 } 582 } 583 } 584 585 protected abstract boolean isCorrectSoapVersion(int contentTypeId); 586 587 protected abstract String getExpectedContentType(); 588 protected abstract String getExpectedAcceptHeader(); 589 590 /** 591 * Sniffs the Content-Type header so that we can determine how to process. 592 * 593 * <p> 594 * In the absence of type attribute we assume it to be text/xml. 595 * That would mean we're easy on accepting the message and 596 * generate the correct thing (as the SWA spec also specifies 597 * that the type parameter should always be text/xml) 598 * 599 * @return 600 * combination of flags, such as PLAIN_XML_CODE and MIME_MULTIPART_CODE. 601 */ 602 // SOAP1.2 allow SOAP1.2 content type 603 static int identifyContentType(ContentType ct) 604 throws SOAPExceptionImpl { 605 // TBD 606 // Is there anything else we need to verify here? 607 608 String primary = ct.getPrimaryType().toLowerCase(); 609 String sub = ct.getSubType().toLowerCase(); 610 611 if (primary.equals("multipart")) { 612 if (sub.equals("related")) { 613 String type = getTypeParameter(ct); 614 if (isEqualToSoap1_1Type(type)) { 615 return (type.equals("application/fastinfoset") ? 616 FI_ENCODED_FLAG : 0) | MIME_MULTIPART_FLAG | SOAP1_1_FLAG; 617 } 618 else if (isEqualToSoap1_2Type(type)) { 619 return (type.equals("application/soap+fastinfoset") ? 620 FI_ENCODED_FLAG : 0) | MIME_MULTIPART_FLAG | SOAP1_2_FLAG; 621 /*} else if (isMimeMultipartXOPPackage(ct)) { 622 return MIME_MULTIPART_XOP_FLAG;*/ 623 } else if (isMimeMultipartXOPSoap1_1Package(ct)) { 624 return MIME_MULTIPART_XOP_SOAP1_1_FLAG; 625 } else if (isMimeMultipartXOPSoap1_2Package(ct)) { 626 return MIME_MULTIPART_XOP_SOAP1_2_FLAG; 627 } else { 628 log.severe("SAAJ0536.soap.content-type.mustbe.multipart"); 629 throw new SOAPExceptionImpl( 630 "Content-Type needs to be Multipart/Related " 631 + "and with \"type=text/xml\" " 632 + "or \"type=application/soap+xml\""); 633 } 634 } else { 635 log.severe("SAAJ0537.soap.invalid.content-type"); 636 throw new SOAPExceptionImpl( 637 "Invalid Content-Type: " + primary + '/' + sub); 638 } 639 } 640 else if (isSoap1_1Type(primary, sub)) { 641 return (primary.equalsIgnoreCase("application") 642 && sub.equalsIgnoreCase("fastinfoset") ? 643 FI_ENCODED_FLAG : 0) 644 | PLAIN_XML_FLAG | SOAP1_1_FLAG; 645 } 646 else if (isSoap1_2Type(primary, sub)) { 647 return (primary.equalsIgnoreCase("application") 648 && sub.equalsIgnoreCase("soap+fastinfoset") ? 649 FI_ENCODED_FLAG : 0) 650 | PLAIN_XML_FLAG | SOAP1_2_FLAG; 651 } else if(isSOAPBodyXOPPackage(ct)){ 652 return XOP_FLAG; 653 } else { 654 log.severe("SAAJ0537.soap.invalid.content-type"); 655 throw new SOAPExceptionImpl( 656 "Invalid Content-Type:" 657 + primary 658 + '/' 659 + sub 660 + ". Is this an error message instead of a SOAP response?"); 661 } 662 } 663 664 /** 665 * Obtains the type parameter of the Content-Type header. Defaults to "text/xml". 666 */ 667 private static String getTypeParameter(ContentType contentType) { 668 String p = contentType.getParameter("type"); 669 if(p!=null) 670 return p.toLowerCase(); 671 else 672 return "text/xml"; 673 } 674 675 public MimeHeaders getMimeHeaders() { 676 return this.headers; 677 } 678 679 final static String getContentType(MimeHeaders headers) { 680 String[] values = headers.getHeader("Content-Type"); 681 if (values == null) 682 return null; 683 else 684 return values[0]; 685 } 686 687 /* 688 * Get the complete ContentType value along with optional parameters. 689 */ 690 public String getContentType() { 691 return getContentType(this.headers); 692 } 693 694 public void setContentType(String type) { 695 headers.setHeader("Content-Type", type); 696 needsSave(); 697 } 698 699 private ContentType contentType() { 700 ContentType ct = null; 701 try { 702 String currentContent = getContentType(); 703 if (currentContent == null) { 704 return this.contentType; 705 } 706 ct = new ContentType(currentContent); 707 } catch (Exception e) { 708 // what to do here? 709 } 710 return ct; 711 } 712 713 /* 714 * Return the MIME type string, without the parameters. 715 */ 716 public String getBaseType() { 717 return contentType().getBaseType(); 718 } 719 720 public void setBaseType(String type) { 721 ContentType ct = contentType(); 722 ct.setParameter("type", type); 723 headers.setHeader("Content-Type", ct.toString()); 724 needsSave(); 725 } 726 727 public String getAction() { 728 return contentType().getParameter("action"); 729 } 730 731 public void setAction(String action) { 732 ContentType ct = contentType(); 733 ct.setParameter("action", action); 734 headers.setHeader("Content-Type", ct.toString()); 735 needsSave(); 736 } 737 738 public String getCharset() { 739 return contentType().getParameter("charset"); 740 } 741 742 public void setCharset(String charset) { 743 ContentType ct = contentType(); 744 ct.setParameter("charset", charset); 745 headers.setHeader("Content-Type", ct.toString()); 746 needsSave(); 747 } 748 749 /** 750 * All write methods (i.e setters) should call this method in 751 * order to make sure that a save is necessary since the state 752 * has been modified. 753 */ 754 private final void needsSave() { 755 saved = false; 756 } 757 758 public boolean saveRequired() { 759 return saved != true; 760 } 761 762 public String getContentDescription() { 763 String[] values = headers.getHeader("Content-Description"); 764 if (values != null && values.length > 0) 765 return values[0]; 766 return null; 767 } 768 769 public void setContentDescription(String description) { 770 headers.setHeader("Content-Description", description); 771 needsSave(); 772 } 773 774 public abstract SOAPPart getSOAPPart(); 775 776 public void removeAllAttachments() { 777 try { 778 initializeAllAttachments(); 779 } catch (Exception e) { 780 throw new RuntimeException(e); 781 } 782 783 if (attachments != null) { 784 attachments.clear(); 785 needsSave(); 786 } 787 } 788 789 public int countAttachments() { 790 try { 791 initializeAllAttachments(); 792 } catch (Exception e) { 793 throw new RuntimeException(e); 794 } 795 if (attachments != null) 796 return attachments.size(); 797 return 0; 798 } 799 800 public void addAttachmentPart(AttachmentPart attachment) { 801 try { 802 initializeAllAttachments(); 803 this.optimizeAttachmentProcessing = true; 804 } catch (Exception e) { 805 throw new RuntimeException(e); 806 } 807 if (attachments == null) 808 attachments = new FinalArrayList(); 809 810 attachments.add(attachment); 811 812 needsSave(); 813 } 814 815 static private final Iterator nullIter = Collections.EMPTY_LIST.iterator(); 816 817 public Iterator getAttachments() { 818 try { 819 initializeAllAttachments(); 820 } catch (Exception e) { 821 throw new RuntimeException(e); 822 } 823 if (attachments == null) 824 return nullIter; 825 return attachments.iterator(); 826 } 827 828 private void setFinalContentType(String charset) { 829 ContentType ct = contentType(); 830 if (ct == null) { 831 ct = new ContentType(); 832 } 833 String[] split = getExpectedContentType().split("/"); 834 ct.setPrimaryType(split[0]); 835 ct.setSubType(split[1]); 836 ct.setParameter("charset", charset); 837 headers.setHeader("Content-Type", ct.toString()); 838 } 839 840 private class MimeMatchingIterator implements Iterator { 841 public MimeMatchingIterator(MimeHeaders headers) { 842 this.headers = headers; 843 this.iter = attachments.iterator(); 844 } 845 846 private Iterator iter; 847 private MimeHeaders headers; 848 private Object nextAttachment; 849 850 public boolean hasNext() { 851 if (nextAttachment == null) 852 nextAttachment = nextMatch(); 853 return nextAttachment != null; 854 } 855 856 public Object next() { 857 if (nextAttachment != null) { 858 Object ret = nextAttachment; 859 nextAttachment = null; 860 return ret; 861 } 862 863 if (hasNext()) 864 return nextAttachment; 865 866 return null; 867 } 868 869 Object nextMatch() { 870 while (iter.hasNext()) { 871 AttachmentPartImpl ap = (AttachmentPartImpl) iter.next(); 872 if (ap.hasAllHeaders(headers)) 873 return ap; 874 } 875 return null; 876 } 877 878 public void remove() { 879 iter.remove(); 880 } 881 } 882 883 public Iterator getAttachments(MimeHeaders headers) { 884 try { 885 initializeAllAttachments(); 886 } catch (Exception e) { 887 throw new RuntimeException(e); 888 } 889 if (attachments == null) 890 return nullIter; 891 892 return new MimeMatchingIterator(headers); 893 } 894 895 public void removeAttachments(MimeHeaders headers) { 896 try { 897 initializeAllAttachments(); 898 } catch (Exception e) { 899 throw new RuntimeException(e); 900 } 901 if (attachments == null) 902 return ; 903 904 Iterator it = new MimeMatchingIterator(headers); 905 while (it.hasNext()) { 906 int index = attachments.indexOf(it.next()); 907 attachments.set(index, null); 908 } 909 FinalArrayList f = new FinalArrayList(); 910 for (int i = 0; i < attachments.size(); i++) { 911 if (attachments.get(i) != null) { 912 f.add(attachments.get(i)); 913 } 914 } 915 attachments = f; 916 // needsSave(); 917 } 918 919 public AttachmentPart createAttachmentPart() { 920 return new AttachmentPartImpl(); 921 } 922 923 public AttachmentPart getAttachment(SOAPElement element) 924 throws SOAPException { 925 try { 926 initializeAllAttachments(); 927 } catch (Exception e) { 928 throw new RuntimeException(e); 929 } 930 String uri; 931 String hrefAttr = element.getAttribute("href"); 932 if ("".equals(hrefAttr)) { 933 Node node = getValueNodeStrict(element); 934 String swaRef = null; 935 if (node != null) { 936 swaRef = node.getValue(); 937 } 938 if (swaRef == null || "".equals(swaRef)) { 939 return null; 940 } else { 941 uri = swaRef; 942 } 943 } else { 944 uri = hrefAttr; 945 } 946 return getAttachmentPart(uri); 947 } 948 949 private Node getValueNodeStrict(SOAPElement element) { 950 Node node = (Node)element.getFirstChild(); 951 if (node != null) { 952 if (node.getNextSibling() == null 953 && node.getNodeType() == org.w3c.dom.Node.TEXT_NODE) { 954 return node; 955 } else { 956 return null; 957 } 958 } 959 return null; 960 } 961 962 963 private AttachmentPart getAttachmentPart(String uri) throws SOAPException { 964 AttachmentPart _part; 965 try { 966 if (uri.startsWith("cid:")) { 967 // rfc2392 968 uri = '<'+uri.substring("cid:".length())+'>'; 969 970 MimeHeaders headersToMatch = new MimeHeaders(); 971 headersToMatch.addHeader(CONTENT_ID, uri); 972 973 Iterator i = this.getAttachments(headersToMatch); 974 _part = (i == null) ? null : (AttachmentPart)i.next(); 975 } else { 976 // try content-location 977 MimeHeaders headersToMatch = new MimeHeaders(); 978 headersToMatch.addHeader(CONTENT_LOCATION, uri); 979 980 Iterator i = this.getAttachments(headersToMatch); 981 _part = (i == null) ? null : (AttachmentPart)i.next(); 982 } 983 984 // try auto-generated JAXRPC CID 985 if (_part == null) { 986 Iterator j = this.getAttachments(); 987 988 while (j.hasNext()) { 989 AttachmentPart p = (AttachmentPart)j.next(); 990 String cl = p.getContentId(); 991 if (cl != null) { 992 // obtain the partname 993 int eqIndex = cl.indexOf("="); 994 if (eqIndex > -1) { 995 cl = cl.substring(1, eqIndex); 996 if (cl.equalsIgnoreCase(uri)) { 997 _part = p; 998 break; 999 } 1000 } 1001 } 1002 } 1003 } 1004 1005 } catch (Exception se) { 1006 log.log(Level.SEVERE, "SAAJ0590.soap.unable.to.locate.attachment", new Object[] {uri}); 1007 throw new SOAPExceptionImpl(se); 1008 } 1009 return _part; 1010 } 1011 1012 private final InputStream getHeaderBytes() 1013 throws IOException { 1014 SOAPPartImpl sp = (SOAPPartImpl) getSOAPPart(); 1015 return sp.getContentAsStream(); 1016 } 1017 1018 private String convertToSingleLine(String contentType) { 1019 StringBuffer buffer = new StringBuffer(); 1020 for (int i = 0; i < contentType.length(); i ++) { 1021 char c = contentType.charAt(i); 1022 if (c != '\r' && c != '\n' && c != '\t') 1023 buffer.append(c); 1024 } 1025 return buffer.toString(); 1026 } 1027 1028 private MimeMultipart getMimeMessage() throws SOAPException { 1029 try { 1030 SOAPPartImpl soapPart = (SOAPPartImpl) getSOAPPart(); 1031 MimeBodyPart mimeSoapPart = soapPart.getMimePart(); 1032 1033 /* 1034 * Get content type from this message instead of soapPart 1035 * to ensure agreement if soapPart is transcoded (XML <-> FI) 1036 */ 1037 ContentType soapPartCtype = new ContentType(getExpectedContentType()); 1038 1039 if (!isFastInfoset) { 1040 soapPartCtype.setParameter("charset", initCharset()); 1041 } 1042 mimeSoapPart.setHeader("Content-Type", soapPartCtype.toString()); 1043 1044 MimeMultipart headerAndBody = null; 1045 1046 if (!switchOffBM && !switchOffLazyAttachment && 1047 (multiPart != null) && !attachmentsInitialized) { 1048 headerAndBody = new BMMimeMultipart(); 1049 headerAndBody.addBodyPart(mimeSoapPart); 1050 if (attachments != null) { 1051 for (Iterator eachAttachment = attachments.iterator(); 1052 eachAttachment.hasNext();) { 1053 headerAndBody.addBodyPart( 1054 ((AttachmentPartImpl) eachAttachment.next()) 1055 .getMimePart()); 1056 } 1057 } 1058 InputStream in = ((BMMimeMultipart)multiPart).getInputStream(); 1059 if (!((BMMimeMultipart)multiPart).lastBodyPartFound() && 1060 !((BMMimeMultipart)multiPart).isEndOfStream()) { 1061 ((BMMimeMultipart)headerAndBody).setInputStream(in); 1062 ((BMMimeMultipart)headerAndBody).setBoundary( 1063 ((BMMimeMultipart)multiPart).getBoundary()); 1064 ((BMMimeMultipart)headerAndBody). 1065 setLazyAttachments(lazyAttachments); 1066 } 1067 1068 } else { 1069 headerAndBody = new MimeMultipart(); 1070 headerAndBody.addBodyPart(mimeSoapPart); 1071 1072 for (Iterator eachAttachement = getAttachments(); 1073 eachAttachement.hasNext(); 1074 ) { 1075 headerAndBody.addBodyPart( 1076 ((AttachmentPartImpl) eachAttachement.next()) 1077 .getMimePart()); 1078 } 1079 } 1080 1081 ContentType contentType = headerAndBody.getContentType(); 1082 1083 ParameterList l = contentType.getParameterList(); 1084 1085 // set content type depending on SOAP version 1086 l.set("type", getExpectedContentType()); 1087 l.set("boundary", contentType.getParameter("boundary")); 1088 ContentType nct = new ContentType("multipart", "related", l); 1089 1090 headers.setHeader( 1091 "Content-Type", 1092 convertToSingleLine(nct.toString())); 1093 // TBD 1094 // Set content length MIME header here. 1095 1096 return headerAndBody; 1097 } catch (SOAPException ex) { 1098 throw ex; 1099 } catch (Throwable ex) { 1100 log.severe("SAAJ0538.soap.cannot.convert.msg.to.multipart.obj"); 1101 throw new SOAPExceptionImpl( 1102 "Unable to convert SOAP message into " 1103 + "a MimeMultipart object", 1104 ex); 1105 } 1106 } 1107 1108 private String initCharset() { 1109 1110 String charset = null; 1111 1112 String[] cts = getMimeHeaders().getHeader("Content-Type"); 1113 if ((cts != null) && (cts[0] != null)) { 1114 charset = getCharsetString(cts[0]); 1115 } 1116 1117 if (charset == null) { 1118 charset = (String) getProperty(CHARACTER_SET_ENCODING); 1119 } 1120 1121 if (charset != null) { 1122 return charset; 1123 } 1124 1125 return "utf-8"; 1126 } 1127 1128 private String getCharsetString(String s) { 1129 try { 1130 int index = s.indexOf(";"); 1131 if(index < 0) 1132 return null; 1133 ParameterList pl = new ParameterList(s.substring(index)); 1134 return pl.get("charset"); 1135 } catch(Exception e) { 1136 return null; 1137 } 1138 } 1139 1140 public void saveChanges() throws SOAPException { 1141 1142 // suck in all the data from the attachments and have it 1143 // ready for writing/sending etc. 1144 1145 String charset = initCharset(); 1146 1147 /*if (countAttachments() == 0) {*/ 1148 int attachmentCount = (attachments == null) ? 0 : attachments.size(); 1149 if (attachmentCount == 0) { 1150 if (!switchOffBM && !switchOffLazyAttachment && 1151 !attachmentsInitialized && (multiPart != null)) { 1152 // so there might be attachments 1153 attachmentCount = 1; 1154 } 1155 } 1156 1157 try { 1158 if ((attachmentCount == 0) && !hasXOPContent()) { 1159 InputStream in; 1160 try{ 1161 /* 1162 * Not sure why this is called getHeaderBytes(), but it actually 1163 * returns the whole message as a byte stream. This stream could 1164 * be either XML of Fast depending on the mode. 1165 */ 1166 in = getHeaderBytes(); 1167 // no attachments, hence this property can be false 1168 this.optimizeAttachmentProcessing = false; 1169 if (SOAPPartImpl.lazyContentLength) { 1170 inputStreamAfterSaveChanges = in; 1171 } 1172 } catch (IOException ex) { 1173 log.severe("SAAJ0539.soap.cannot.get.header.stream"); 1174 throw new SOAPExceptionImpl( 1175 "Unable to get header stream in saveChanges: ", 1176 ex); 1177 } 1178 1179 if (in instanceof ByteInputStream) { 1180 ByteInputStream bIn = (ByteInputStream)in; 1181 messageBytes = bIn.getBytes(); 1182 messageByteCount = bIn.getCount(); 1183 } 1184 1185 setFinalContentType(charset); 1186 /* 1187 headers.setHeader( 1188 "Content-Type", 1189 getExpectedContentType() + 1190 (isFastInfoset ? "" : "; charset=" + charset));*/ 1191 if (messageByteCount > 0) { 1192 headers.setHeader( 1193 "Content-Length", 1194 Integer.toString(messageByteCount)); 1195 } 1196 } else { 1197 if(hasXOPContent()) 1198 mmp = getXOPMessage(); 1199 else 1200 mmp = getMimeMessage(); 1201 } 1202 } catch (Throwable ex) { 1203 log.severe("SAAJ0540.soap.err.saving.multipart.msg"); 1204 throw new SOAPExceptionImpl( 1205 "Error during saving a multipart message", 1206 ex); 1207 } 1208 1209 // FIX ME -- SOAP Action replaced by Content-Type optional parameter action 1210 /* 1211 if(isCorrectSoapVersion(SOAP1_1_FLAG)) { 1212 1213 String[] soapAction = headers.getHeader("SOAPAction"); 1214 1215 if (soapAction == null || soapAction.length == 0) 1216 headers.setHeader("SOAPAction", "\"\""); 1217 1218 } 1219 */ 1220 1221 saved = true; 1222 } 1223 1224 private MimeMultipart getXOPMessage() throws SOAPException { 1225 try { 1226 MimeMultipart headerAndBody = new MimeMultipart(); 1227 SOAPPartImpl soapPart = (SOAPPartImpl)getSOAPPart(); 1228 MimeBodyPart mimeSoapPart = soapPart.getMimePart(); 1229 ContentType soapPartCtype = 1230 new ContentType("application/xop+xml"); 1231 soapPartCtype.setParameter("type", getExpectedContentType()); 1232 String charset = initCharset(); 1233 soapPartCtype.setParameter("charset", charset); 1234 mimeSoapPart.setHeader("Content-Type", soapPartCtype.toString()); 1235 headerAndBody.addBodyPart(mimeSoapPart); 1236 1237 for (Iterator eachAttachement = getAttachments(); 1238 eachAttachement.hasNext(); 1239 ) { 1240 headerAndBody.addBodyPart( 1241 ((AttachmentPartImpl) eachAttachement.next()) 1242 .getMimePart()); 1243 } 1244 1245 ContentType contentType = headerAndBody.getContentType(); 1246 1247 ParameterList l = contentType.getParameterList(); 1248 1249 //lets not write start-info for now till we get servlet fix done 1250 l.set("start-info", getExpectedContentType());//+";charset="+initCharset()); 1251 1252 // set content type depending on SOAP version 1253 l.set("type", "application/xop+xml"); 1254 1255 if (isCorrectSoapVersion(SOAP1_2_FLAG)) { 1256 String action = getAction(); 1257 if(action != null) 1258 l.set("action", action); 1259 } 1260 1261 l.set("boundary", contentType.getParameter("boundary")); 1262 ContentType nct = new ContentType("Multipart", "Related", l); 1263 headers.setHeader( 1264 "Content-Type", 1265 convertToSingleLine(nct.toString())); 1266 // TBD 1267 // Set content length MIME header here. 1268 1269 return headerAndBody; 1270 } catch (SOAPException ex) { 1271 throw ex; 1272 } catch (Throwable ex) { 1273 log.severe("SAAJ0538.soap.cannot.convert.msg.to.multipart.obj"); 1274 throw new SOAPExceptionImpl( 1275 "Unable to convert SOAP message into " 1276 + "a MimeMultipart object", 1277 ex); 1278 } 1279 1280 } 1281 1282 private boolean hasXOPContent() throws ParseException { 1283 String type = getContentType(); 1284 if(type == null) 1285 return false; 1286 ContentType ct = new ContentType(type); 1287 //return isMimeMultipartXOPPackage(ct) || isSOAPBodyXOPPackage(ct); 1288 return isMimeMultipartXOPSoap1_1Package(ct) || 1289 isMimeMultipartXOPSoap1_2Package(ct) || isSOAPBodyXOPPackage(ct); 1290 1291 } 1292 1293 public void writeTo(OutputStream out) throws SOAPException, IOException { 1294 if (saveRequired()){ 1295 this.optimizeAttachmentProcessing = true; 1296 saveChanges(); 1297 } 1298 1299 if(!optimizeAttachmentProcessing){ 1300 if (SOAPPartImpl.lazyContentLength && messageByteCount <= 0) { 1301 byte[] buf = new byte[1024]; 1302 1303 int length = 0; 1304 while( (length = inputStreamAfterSaveChanges.read(buf)) != -1) { 1305 out.write(buf,0, length); 1306 messageByteCount += length; 1307 } 1308 if (messageByteCount > 0) { 1309 headers.setHeader( 1310 "Content-Length", 1311 Integer.toString(messageByteCount)); 1312 } 1313 } else { 1314 out.write(messageBytes, 0, messageByteCount); 1315 } 1316 } 1317 else{ 1318 try{ 1319 if(hasXOPContent()){ 1320 mmp.writeTo(out); 1321 }else{ 1322 mmp.writeTo(out); 1323 if (!switchOffBM && !switchOffLazyAttachment && 1324 (multiPart != null) && !attachmentsInitialized) { 1325 ((BMMimeMultipart)multiPart).setInputStream( 1326 ((BMMimeMultipart)mmp).getInputStream()); 1327 } 1328 } 1329 } catch(Exception ex){ 1330 log.severe("SAAJ0540.soap.err.saving.multipart.msg"); 1331 throw new SOAPExceptionImpl( 1332 "Error during saving a multipart message", 1333 ex); 1334 } 1335 } 1336 1337 if(isCorrectSoapVersion(SOAP1_1_FLAG)) { 1338 1339 String[] soapAction = headers.getHeader("SOAPAction"); 1340 1341 if (soapAction == null || soapAction.length == 0) 1342 headers.setHeader("SOAPAction", "\"\""); 1343 1344 } 1345 1346 messageBytes = null; 1347 needsSave(); 1348 } 1349 1350 public SOAPBody getSOAPBody() throws SOAPException { 1351 SOAPBody body = getSOAPPart().getEnvelope().getBody(); 1352 /*if (body == null) { 1353 throw new SOAPException("No SOAP Body was found in the SOAP Message"); 1354 }*/ 1355 return body; 1356 } 1357 1358 public SOAPHeader getSOAPHeader() throws SOAPException { 1359 SOAPHeader hdr = getSOAPPart().getEnvelope().getHeader(); 1360 /*if (hdr == null) { 1361 throw new SOAPException("No SOAP Header was found in the SOAP Message"); 1362 }*/ 1363 return hdr; 1364 } 1365 1366 private void initializeAllAttachments () 1367 throws MessagingException, SOAPException { 1368 if (switchOffBM || switchOffLazyAttachment) { 1369 return; 1370 } 1371 1372 if (attachmentsInitialized || (multiPart == null)) { 1373 return; 1374 } 1375 1376 if (attachments == null) 1377 attachments = new FinalArrayList(); 1378 1379 int count = multiPart.getCount(); 1380 for (int i=0; i < count; i++ ) { 1381 initializeAttachment(multiPart.getBodyPart(i)); 1382 } 1383 attachmentsInitialized = true; 1384 //multiPart = null; 1385 needsSave(); 1386 } 1387 1388 private void initializeAttachment(MimeBodyPart mbp) throws SOAPException { 1389 AttachmentPartImpl attachmentPart = new AttachmentPartImpl(); 1390 DataHandler attachmentHandler = mbp.getDataHandler(); 1391 attachmentPart.setDataHandler(attachmentHandler); 1392 1393 AttachmentPartImpl.copyMimeHeaders(mbp, attachmentPart); 1394 attachments.add(attachmentPart); 1395 } 1396 1397 private void initializeAttachment(MimeMultipart multiPart, int i) 1398 throws Exception { 1399 MimeBodyPart currentBodyPart = multiPart.getBodyPart(i); 1400 AttachmentPartImpl attachmentPart = new AttachmentPartImpl(); 1401 1402 DataHandler attachmentHandler = currentBodyPart.getDataHandler(); 1403 attachmentPart.setDataHandler(attachmentHandler); 1404 1405 AttachmentPartImpl.copyMimeHeaders(currentBodyPart, attachmentPart); 1406 addAttachmentPart(attachmentPart); 1407 } 1408 1409 private void setMimeHeaders(SOAPPart soapPart, 1410 MimeBodyPart soapMessagePart) throws Exception { 1411 1412 // first remove the existing content-type 1413 soapPart.removeAllMimeHeaders(); 1414 // add everything present in soapMessagePart 1415 List headers = soapMessagePart.getAllHeaders(); 1416 int sz = headers.size(); 1417 for( int i=0; i<sz; i++ ) { 1418 Header h = (Header) headers.get(i); 1419 soapPart.addMimeHeader(h.getName(), h.getValue()); 1420 } 1421 } 1422 1423 private void initCharsetProperty(ContentType contentType) { 1424 String charset = contentType.getParameter("charset"); 1425 if (charset != null) { 1426 ((SOAPPartImpl) getSOAPPart()).setSourceCharsetEncoding(charset); 1427 if(!charset.equalsIgnoreCase("utf-8")) 1428 setProperty(CHARACTER_SET_ENCODING, charset); 1429 } 1430 } 1431 1432 public void setLazyAttachments(boolean flag) { 1433 lazyAttachments = flag; 1434 } 1435 1436 }