src/share/classes/com/sun/xml/internal/messaging/saaj/soap/MessageImpl.java

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

mercurial