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