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