View Javadoc

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