View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.impl.wsdl.submit.transports.http.support.attachments;
14  
15  import java.io.File;
16  import java.io.FileInputStream;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.net.URI;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import javax.activation.DataHandler;
28  import javax.mail.MessagingException;
29  import javax.mail.Session;
30  import javax.mail.internet.MimeBodyPart;
31  import javax.mail.internet.MimeMultipart;
32  import javax.mail.internet.PreencodedMimeBodyPart;
33  import javax.wsdl.Input;
34  import javax.wsdl.Output;
35  import javax.xml.namespace.QName;
36  
37  import org.apache.commons.codec.binary.Base64;
38  import org.apache.commons.codec.binary.Hex;
39  import org.apache.log4j.Logger;
40  import org.apache.xmlbeans.SchemaType;
41  import org.apache.xmlbeans.XmlBase64Binary;
42  import org.apache.xmlbeans.XmlCursor;
43  import org.apache.xmlbeans.XmlHexBinary;
44  import org.apache.xmlbeans.XmlObject;
45  
46  import com.eviware.soapui.SoapUI;
47  import com.eviware.soapui.config.PartsConfig;
48  import com.eviware.soapui.config.PartsConfig.Part;
49  import com.eviware.soapui.impl.wsdl.AttachmentContainer;
50  import com.eviware.soapui.impl.wsdl.HttpAttachmentPart;
51  import com.eviware.soapui.impl.wsdl.WsdlAttachmentContainer;
52  import com.eviware.soapui.impl.wsdl.WsdlOperation;
53  import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
54  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
55  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
56  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
57  import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
58  import com.eviware.soapui.model.iface.Attachment;
59  import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
60  import com.eviware.soapui.support.StringUtils;
61  import com.eviware.soapui.support.Tools;
62  import com.eviware.soapui.support.editor.inspectors.attachments.ContentTypeHandler;
63  import com.eviware.soapui.support.types.StringToStringMap;
64  import com.eviware.soapui.support.xml.XmlObjectTreeModel;
65  import com.eviware.soapui.support.xml.XmlUtils;
66  import com.eviware.soapui.support.xml.XmlObjectTreeModel.XmlTreeNode;
67  
68  /***
69   * Attachment-related utility classes
70   * 
71   * @author ole.matzura
72   */
73  
74  public class AttachmentUtils
75  {
76  	private final static Logger log = Logger.getLogger( AttachmentUtils.class );
77  	private static final QName XMLMIME_CONTENTTYPE_200505 = new QName( "http://www.w3.org/2005/05/xmlmime",
78  			"contentType" );
79  	private static final QName XMLMIME_CONTENTTYPE_200411 = new QName( "http://www.w3.org/2004/11/xmlmime",
80  			"contentType" );
81  	private static final QName SWAREF_QNAME = new QName( "http://ws-i.org/profiles/basic/1.1/xsd", "swaRef" );
82  	public static final QName XOP_HREF_QNAME = new QName( "href" );
83  	private static final QName XOP_INCLUDE_QNAME = new QName( "http://www.w3.org/2004/08/xop/include", "Include" );
84  	public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>";
85  
86  	public static boolean prepareMessagePart( WsdlAttachmentContainer container, MimeMultipart mp,
87  			MessageXmlPart messagePart, StringToStringMap contentIds ) throws Exception, MessagingException
88  	{
89  		boolean isXop = false;
90  
91  		XmlObjectTreeModel treeModel = null;
92  		XmlCursor cursor = messagePart.newCursor();
93  		XmlObject rootXmlObject = cursor.getObject();
94  
95  		try
96  		{
97  			while( !cursor.isEnddoc() )
98  			{
99  				if( cursor.isContainer() )
100 				{
101 					// could be an attachment part (as of "old" SwA specs which
102 					// specify a content
103 					// element referring to the attachment)
104 					if( messagePart.isAttachmentPart() )
105 					{
106 						String href = cursor.getAttributeText( XOP_HREF_QNAME );
107 						if( href != null && href.length() > 0 )
108 						{
109 							contentIds.put( messagePart.getPart().getName(), href );
110 						}
111 
112 						break;
113 					}
114 
115 					XmlObject xmlObj = cursor.getObject();
116 					SchemaType schemaType = xmlObj.schemaType();
117 					if( schemaType.isNoType() )
118 					{
119 						if( treeModel == null )
120 						{
121 							treeModel = new XmlObjectTreeModel( messagePart.getSchemaType().getTypeSystem(), rootXmlObject );
122 						}
123 
124 						XmlTreeNode tn = treeModel.getXmlTreeNode( xmlObj );
125 						if( tn != null )
126 							schemaType = tn.getSchemaType();
127 					}
128 
129 					if( AttachmentUtils.isSwaRefType( schemaType ) )
130 					{
131 						String textContent = XmlUtils.getNodeValue( cursor.getDomNode() );
132 						if( StringUtils.hasContent( textContent ) && textContent.startsWith( "cid:" ) )
133 						{
134 							textContent = textContent.substring( 4 );
135 
136 							try
137 							{
138 								// is the textcontent already a URI?
139 								new URI( textContent );
140 								contentIds.put( textContent, textContent );
141 							}
142 							catch( RuntimeException e )
143 							{
144 								// not a URI.. try to create one..
145 								String contentId = textContent + "@soapui.org";
146 								cursor.setTextValue( "cid:" + contentId );
147 								contentIds.put( textContent, contentId );
148 							}
149 						}
150 					}
151 					else if( AttachmentUtils.isXopInclude( schemaType ) )
152 					{
153 						String contentId = cursor.getAttributeText( new QName( "href" ) );
154 						if( contentId != null && contentId.length() > 0 )
155 						{
156 							contentIds.put( contentId, contentId );
157 							isXop = true;
158 
159 							Attachment[] attachments = container.getAttachmentsForPart( contentId );
160 							if( attachments.length == 1 )
161 							{
162 								XmlCursor cur = cursor.newCursor();
163 								if( cur.toParent() )
164 								{
165 									String contentType = getXmlMimeContentType( cur );
166 									if( contentType != null && contentType.length() > 0 )
167 										attachments[0].setContentType( contentType );
168 								}
169 
170 								cur.dispose();
171 							}
172 						}
173 					}
174 					else
175 					{
176 						// extract contentId
177 						String textContent = XmlUtils.getNodeValue( cursor.getDomNode() );
178 						if( StringUtils.hasContent( textContent ) )
179 						{
180 							Attachment attachment = null;
181 							boolean isXopAttachment = false;
182 
183 							// is content a reference to a file?
184 							if( container.isInlineFilesEnabled() && textContent.startsWith( "file:" ) )
185 							{
186 								String filename = textContent.substring( 5 );
187 								if( container.isMtomEnabled() )
188 								{
189 									MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
190 									String xmimeContentType = getXmlMimeContentType( cursor );
191 
192 									if( StringUtils.isNullOrEmpty( xmimeContentType ) )
193 										xmimeContentType = ContentTypeHandler.getContentTypeFromFilename( filename );
194 
195 									part.setDataHandler( new DataHandler( new XOPPartDataSource( new File( filename ),
196 											xmimeContentType, schemaType ) ) );
197 									part.setContentID( "<" + filename + ">" );
198 									mp.addBodyPart( part );
199 
200 									isXopAttachment = true;
201 								}
202 								else
203 								{
204 									inlineData( cursor, schemaType, new FileInputStream( filename ) );
205 								}
206 							}
207 							// is content a reference to an attachment?
208 							else if( textContent.startsWith( "cid:" ) )
209 							{
210 								textContent = textContent.substring( 4 );
211 
212 								Attachment[] attachments = container.getAttachmentsForPart( textContent );
213 								if( attachments.length == 1 )
214 								{
215 									attachment = attachments[0];
216 								}
217 								else if( attachments.length > 1 )
218 								{
219 									attachment = buildMulitpartAttachment( attachments );
220 								}
221 
222 								isXopAttachment = container.isMtomEnabled();
223 								contentIds.put( textContent, textContent );
224 							}
225 							// content should be binary data; is this an XOP element
226 							// which should be serialized with MTOM?
227 							else if( container.isMtomEnabled()
228 									&& ( SchemaUtils.isBinaryType( schemaType ) || SchemaUtils.isAnyType( schemaType ) ) )
229 							{
230 								if( "true".equals( System.getProperty( "soapui.mtom.strict" ) ) )
231 								{
232 									if( SchemaUtils.isAnyType( schemaType ) )
233 									{
234 										textContent = null;
235 									}
236 									else
237 									{
238 										for( int c = 0; c < textContent.length(); c++ )
239 										{
240 											if( Character.isWhitespace( textContent.charAt( c ) ) )
241 											{
242 												textContent = null;
243 												break;
244 											}
245 										}
246 									}
247 								}
248 
249 								if( textContent != null )
250 								{
251 									MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
252 									String xmimeContentType = getXmlMimeContentType( cursor );
253 
254 									part.setDataHandler( new DataHandler( new XOPPartDataSource( textContent, xmimeContentType,
255 											schemaType ) ) );
256 
257 									textContent = "http://www.soapui.org/" + System.nanoTime();
258 
259 									part.setContentID( "<" + textContent + ">" );
260 									mp.addBodyPart( part );
261 
262 									isXopAttachment = true;
263 								}
264 							}
265 							else if( container.isInlineFilesEnabled()
266 									&& container.getAttachmentsForPart( textContent ) != null )
267 							{
268 								attachment = container.getAttachmentsForPart( textContent )[0];
269 							}
270 
271 							// add XOP include?
272 							if( isXopAttachment && container.isMtomEnabled() )
273 							{
274 								buildXopInclude( cursor, textContent );
275 								isXop = true;
276 							}
277 							// inline?
278 							else if( attachment != null )
279 							{
280 								inlineAttachment( cursor, schemaType, attachment );
281 							}
282 						}
283 					}
284 				}
285 
286 				cursor.toNextToken();
287 			}
288 		}
289 		finally
290 		{
291 			cursor.dispose();
292 		}
293 
294 		return isXop;
295 	}
296 
297 	private static void inlineAttachment( XmlCursor cursor, SchemaType schemaType, Attachment attachment )
298 			throws Exception
299 	{
300 		inlineData( cursor, schemaType, attachment.getInputStream() );
301 	}
302 
303 	private static void inlineData( XmlCursor cursor, SchemaType schemaType, InputStream in ) throws IOException
304 	{
305 		String content = null;
306 		byte[] data = Tools.readAll( in, -1 ).toByteArray();
307 
308 		if( SchemaUtils.isInstanceOf( schemaType, XmlHexBinary.type ) )
309 		{
310 			content = new String( Hex.encodeHex( data ) );
311 		}
312 		else if( SchemaUtils.isInstanceOf( schemaType, XmlBase64Binary.type ) )
313 		{
314 			content = new String( Base64.encodeBase64( data ) );
315 		}
316 
317 		XmlCursor c = cursor.newCursor();
318 		c.setTextValue( content );
319 		c.dispose();
320 	}
321 
322 	private static void buildXopInclude( XmlCursor cursor, String contentId )
323 	{
324 		// build xop:Include
325 		XmlCursor c = cursor.newCursor();
326 		c.removeXmlContents();
327 		c.toFirstContentToken();
328 		c.beginElement( XOP_INCLUDE_QNAME );
329 		c.insertAttributeWithValue( XOP_HREF_QNAME, "cid:" + contentId );
330 		c.toNextSibling();
331 		c.removeXml();
332 		c.dispose();
333 	}
334 
335 	private static Attachment buildMulitpartAttachment( Attachment[] attachments )
336 	{
337 		System.out.println( "buildMulitpartAttachment(Attachment[] attachments) not implemented!" );
338 		return null;
339 	}
340 
341 	public static String buildRootPartContentType( String action, SoapVersion soapVersion )
342 	{
343 		String contentType = "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType();
344 		if( soapVersion == SoapVersion.Soap12 )
345 			contentType += "; action=//\"" + action + "//\"";
346 
347 		return contentType + "\"";
348 	}
349 
350 	public static String buildMTOMContentType( String header, String action, SoapVersion soapVersion )
351 	{
352 		int ix = header.indexOf( "boundary" );
353 		String contentType = "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; "
354 				+ "start-info=\"" + soapVersion.getContentType();
355 
356 		if( soapVersion == SoapVersion.Soap12 && action != null )
357 			contentType += "\"; action=\"" + action;
358 
359 		// nested or not? see
360 		// http://www.eviware.com/forums/index.php?topic=2866.new#new
361 		// contentType += "; action=//\"" + action + "//\"\"; action=\"" + action;
362 
363 		return contentType + "\"; " + header.substring( ix );
364 	}
365 
366 	public static boolean isSwaRefType( SchemaType schemaType )
367 	{
368 		return schemaType.getName() != null && schemaType.getName().equals( SWAREF_QNAME );
369 	}
370 
371 	public static String getXmlMimeContentType( XmlCursor cursor )
372 	{
373 		String attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200411 );
374 		if( attributeText == null )
375 			attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200505 );
376 		return attributeText;
377 	}
378 
379 	public static AttachmentEncoding getAttachmentEncoding( WsdlOperation operation,
380 			HttpAttachmentPart httpAttachmentPart, boolean isResponse )
381 	{
382 		if( httpAttachmentPart.getSchemaType() != null )
383 		{
384 			if( SchemaUtils.isInstanceOf( httpAttachmentPart.getSchemaType(), XmlBase64Binary.type ) )
385 				return AttachmentEncoding.BASE64;
386 			else if( SchemaUtils.isInstanceOf( httpAttachmentPart.getSchemaType(), XmlHexBinary.type ) )
387 				return AttachmentEncoding.HEX;
388 		}
389 
390 		return getAttachmentEncoding( operation, httpAttachmentPart.getName(), isResponse );
391 	}
392 
393 	public static AttachmentEncoding getAttachmentEncoding( WsdlOperation operation, String partName, boolean isResponse )
394 	{
395 		// make sure we have access
396 		if( operation == null || operation.getBindingOperation() == null
397 				|| operation.getBindingOperation().getOperation() == null )
398 		{
399 			return AttachmentEncoding.NONE;
400 		}
401 
402 		javax.wsdl.Part part = null;
403 
404 		if( isResponse )
405 		{
406 			Output output = operation.getBindingOperation().getOperation().getOutput();
407 			if( output == null || output.getMessage() == null )
408 			{
409 				return AttachmentEncoding.NONE;
410 			}
411 			else
412 			{
413 				part = output.getMessage().getPart( partName );
414 			}
415 		}
416 		else
417 		{
418 			Input input = operation.getBindingOperation().getOperation().getInput();
419 			if( input == null || input.getMessage() == null )
420 			{
421 				return AttachmentEncoding.NONE;
422 			}
423 			else
424 			{
425 				part = input.getMessage().getPart( partName );
426 			}
427 		}
428 
429 		if( part != null )
430 		{
431 			QName typeName = part.getTypeName();
432 			if( typeName.getNamespaceURI().equals( "http://www.w3.org/2001/XMLSchema" ) )
433 			{
434 				if( typeName.getLocalPart().equals( "base64Binary" ) )
435 				{
436 					return AttachmentEncoding.BASE64;
437 				}
438 				else if( typeName.getLocalPart().equals( "hexBinary" ) )
439 				{
440 					return AttachmentEncoding.HEX;
441 				}
442 			}
443 		}
444 
445 		return AttachmentEncoding.NONE;
446 	}
447 
448 	public static boolean isXopInclude( SchemaType schemaType )
449 	{
450 		return XOP_INCLUDE_QNAME.equals( schemaType.getName() );
451 	}
452 
453 	public static List<HttpAttachmentPart> extractAttachmentParts( WsdlOperation operation, String messageContent,
454 			boolean addAnonymous, boolean isResponse, boolean forceMtom )
455 	{
456 		List<HttpAttachmentPart> result = new ArrayList<HttpAttachmentPart>();
457 
458 		PartsConfig messageParts = isResponse ? operation.getConfig().getResponseParts() : operation.getConfig()
459 				.getRequestParts();
460 		if( messageParts != null )
461 		{
462 			for( Part part : messageParts.getPartList() )
463 			{
464 				HttpAttachmentPart attachmentPart = new HttpAttachmentPart( part.getName(), part.getContentTypeList() );
465 				attachmentPart.setType( Attachment.AttachmentType.MIME );
466 				result.add( attachmentPart );
467 			}
468 		}
469 
470 		if( messageContent.length() > 0 )
471 		{
472 			WsdlContext wsdlContext = operation.getInterface().getWsdlContext();
473 			WsdlValidator validator = new WsdlValidator( wsdlContext );
474 			try
475 			{
476 				XmlObject[] requestDocuments = validator.getMessageParts( messageContent, operation.getName(), isResponse );
477 
478 				for( XmlObject partDoc : requestDocuments )
479 				{
480 					XmlCursor cursor = partDoc.newCursor();
481 					while( !cursor.isEnddoc() )
482 					{
483 						if( cursor.isContainer() )
484 						{
485 							SchemaType schemaType = cursor.getObject().schemaType();
486 							if( schemaType != null )
487 							{
488 								String attributeText = AttachmentUtils.getXmlMimeContentType( cursor );
489 
490 								// xop?
491 								if( SchemaUtils.isBinaryType( schemaType ) || SchemaUtils.isAnyType( schemaType ) )
492 								{
493 									String contentId = cursor.getTextValue();
494 									if( contentId.startsWith( "cid:" ) )
495 									{
496 										HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId.substring( 4 ),
497 												attributeText );
498 										attachmentPart
499 												.setType( attributeText == null && !forceMtom ? Attachment.AttachmentType.CONTENT
500 														: Attachment.AttachmentType.XOP );
501 										result.add( attachmentPart );
502 									}
503 								}
504 								else if( AttachmentUtils.isXopInclude( schemaType ) )
505 								{
506 									String contentId = cursor.getAttributeText( new QName( "href" ) );
507 									if( contentId != null && contentId.length() > 0 )
508 									{
509 										HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId, attributeText );
510 										attachmentPart.setType( Attachment.AttachmentType.XOP );
511 										result.add( attachmentPart );
512 									}
513 								}
514 								// swaref?
515 								else if( AttachmentUtils.isSwaRefType( schemaType ) )
516 								{
517 									String contentId = cursor.getTextValue();
518 									if( contentId.startsWith( "cid:" ) )
519 									{
520 										HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId.substring( 4 ),
521 												attributeText );
522 										attachmentPart.setType( Attachment.AttachmentType.SWAREF );
523 										result.add( attachmentPart );
524 									}
525 								}
526 							}
527 						}
528 
529 						cursor.toNextToken();
530 					}
531 				}
532 			}
533 			catch( Exception e )
534 			{
535 				if( e instanceof NullPointerException )
536 					SoapUI.logError( e );
537 				log.warn( e.getMessage() );
538 			}
539 		}
540 
541 		if( addAnonymous )
542 			result.add( new HttpAttachmentPart() );
543 
544 		return result;
545 	}
546 
547 	/***
548 	 * Adds defined attachments as mimeparts
549 	 */
550 
551 	public static void addMimeParts( AttachmentContainer container, List<Attachment> attachments, MimeMultipart mp,
552 			StringToStringMap contentIds ) throws MessagingException
553 	{
554 		// no multipart handling?
555 		if( !container.isMultipartEnabled() )
556 		{
557 			for( int c = 0; c < attachments.size(); c++ )
558 			{
559 				Attachment att = attachments.get( c );
560 				if( att.getAttachmentType() != Attachment.AttachmentType.CONTENT )
561 				{
562 					addSingleAttachment( mp, contentIds, att );
563 				}
564 			}
565 		}
566 		else
567 		{
568 			// first identify if any part has more than one attachments
569 			Map<String, List<Attachment>> attachmentsMap = new HashMap<String, List<Attachment>>();
570 			for( int c = 0; c < attachments.size(); c++ )
571 			{
572 				Attachment att = attachments.get( c );
573 				if( att.getAttachmentType() == Attachment.AttachmentType.CONTENT )
574 					continue;
575 
576 				String partName = att.getPart();
577 
578 				if( !attachmentsMap.containsKey( partName ) )
579 				{
580 					attachmentsMap.put( partName, new ArrayList<Attachment>() );
581 				}
582 
583 				attachmentsMap.get( partName ).add( att );
584 			}
585 
586 			// add attachments
587 			for( Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); )
588 			{
589 				attachments = attachmentsMap.get( i.next() );
590 				if( attachments.size() == 1 )
591 				{
592 					Attachment att = attachments.get( 0 );
593 					addSingleAttachment( mp, contentIds, att );
594 				}
595 				// more than one attachment with the same part -> create multipart
596 				// attachment
597 				else if( attachments.size() > 1 )
598 				{
599 					addMultipartAttachment( mp, contentIds, attachments );
600 				}
601 			}
602 		}
603 	}
604 
605 	/***
606 	 * Adds a mulitpart MimeBodyPart from an array of attachments
607 	 */
608 
609 	public static void addMultipartAttachment( MimeMultipart mp, StringToStringMap contentIds,
610 			List<Attachment> attachments ) throws MessagingException
611 	{
612 		MimeMultipart multipart = new MimeMultipart( "mixed" );
613 		for( int c = 0; c < attachments.size(); c++ )
614 		{
615 			Attachment att = attachments.get( c );
616 			String contentType = att.getContentType();
617 
618 			MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart(
619 					"binary" );
620 
621 			part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ) );
622 			initPartContentId( contentIds, part, att, false );
623 			multipart.addBodyPart( part );
624 		}
625 
626 		MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
627 		part.setDataHandler( new DataHandler( new MultipartAttachmentDataSource( multipart ) ) );
628 
629 		Attachment attachment = attachments.get( 0 );
630 		initPartContentId( contentIds, part, attachment, true );
631 
632 		mp.addBodyPart( part );
633 	}
634 
635 	public static void initPartContentId( StringToStringMap contentIds, MimeBodyPart part, Attachment attachment,
636 			boolean isMultipart ) throws MessagingException
637 	{
638 		String partName = attachment.getPart();
639 
640 		String contentID = attachment.getContentID();
641 		if( contentID != null )
642 		{
643 			contentID = contentID.trim();
644 			int ix = contentID.indexOf( ' ' );
645 			if( ix != -1 )
646 				part.setContentID( "<" + ( isMultipart ? contentID.substring( ix + 1 ) : contentID.substring( 0, ix ) )
647 						+ ">" );
648 			else
649 			{
650 				if( !contentID.startsWith( "<" ) )
651 					contentID = "<" + contentID;
652 
653 				if( !contentID.endsWith( ">" ) )
654 					contentID = contentID + ">";
655 
656 				part.setContentID( contentID );
657 			}
658 		}
659 		else if( partName != null && !partName.equals( HttpAttachmentPart.ANONYMOUS_NAME ) )
660 		{
661 			if( contentIds.containsKey( partName ) )
662 			{
663 				part.setContentID( "<" + contentIds.get( partName ) + ">" );
664 			}
665 			else
666 			{
667 				part.setContentID( "<" + partName + "=" + System.nanoTime() + "@soapui.org>" );
668 			}
669 		}
670 	}
671 
672 	/***
673 	 * Adds a simple MimeBodyPart from an attachment
674 	 */
675 
676 	public static void addSingleAttachment( MimeMultipart mp, StringToStringMap contentIds, Attachment att )
677 			throws MessagingException
678 	{
679 		String contentType = att.getContentType();
680 		MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart()
681 				: new PreencodedMimeBodyPart( "binary" );
682 
683 		part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ) );
684 		initPartContentId( contentIds, part, att, false );
685 
686 		mp.addBodyPart( part );
687 	}
688 
689 	public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance( new Properties() );
690 }