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