View Javadoc

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