001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.impl.wsdl.submit.transports.http;
014:
015: import java.io.File;
016: import java.io.FileInputStream;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.net.URI;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Properties;
026:
027: import javax.activation.DataHandler;
028: import javax.mail.MessagingException;
029: import javax.mail.Session;
030: import javax.mail.internet.MimeBodyPart;
031: import javax.mail.internet.MimeMultipart;
032: import javax.mail.internet.PreencodedMimeBodyPart;
033: import javax.wsdl.Input;
034: import javax.wsdl.Output;
035: import javax.xml.namespace.QName;
036:
037: import org.apache.commons.codec.binary.Base64;
038: import org.apache.commons.codec.binary.Hex;
039: import org.apache.log4j.Logger;
040: import org.apache.xmlbeans.SchemaType;
041: import org.apache.xmlbeans.XmlBase64Binary;
042: import org.apache.xmlbeans.XmlCursor;
043: import org.apache.xmlbeans.XmlHexBinary;
044: import org.apache.xmlbeans.XmlObject;
045:
046: import com.eviware.soapui.config.PartsConfig;
047: import com.eviware.soapui.config.PartsConfig.Part;
048: import com.eviware.soapui.impl.wsdl.AttachmentContainer;
049: import com.eviware.soapui.impl.wsdl.WsdlAttachmentPart;
050: import com.eviware.soapui.impl.wsdl.WsdlInterface;
051: import com.eviware.soapui.impl.wsdl.WsdlOperation;
052: import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
053: import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
054: import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
055: import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
056: import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
057: import com.eviware.soapui.model.iface.Attachment;
058: import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
059: import com.eviware.soapui.support.Tools;
060: import com.eviware.soapui.support.types.StringToStringMap;
061:
062: /**
063: * Attachment-related utility classes
064: *
065: * @author ole.matzura
066: */
067:
068: public class AttachmentUtils {
069: private final static Logger log = Logger
070: .getLogger(AttachmentUtils.class);
071: private static final QName XMLMIME_CONTENTTYPE_200505 = new QName(
072: "http://www.w3.org/2005/05/xmlmime", "contentType");
073: private static final QName XMLMIME_CONTENTTYPE_200411 = new QName(
074: "http://www.w3.org/2004/11/xmlmime", "contentType");
075: private static final QName SWAREF_QNAME = new QName(
076: "http://ws-i.org/profiles/basic/1.1/xsd", "swaRef");
077: public static final QName XOP_HREF_QNAME = new QName("href");
078: private static final QName XOP_INCLUDE_QNAME = new QName(
079: "http://www.w3.org/2004/08/xop/include", "Include");
080: public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>";
081:
082: public static boolean prepareMessagePart(
083: AttachmentContainer container, MimeMultipart mp,
084: MessageXmlPart messagePart, StringToStringMap contentIds)
085: throws Exception, MessagingException {
086: boolean isXop = false;
087:
088: XmlCursor cursor = messagePart.newCursor();
089:
090: try {
091: while (!cursor.isEnddoc()) {
092: if (cursor.isContainer()) {
093: // could be an attachment part (as of "old" SwA specs which specify a content
094: // element referring to the attachment)
095: if (messagePart.isAttachmentPart()) {
096: String href = cursor
097: .getAttributeText(XOP_HREF_QNAME);
098: if (href != null && href.length() > 0) {
099: contentIds.put(messagePart.getPart()
100: .getName(), href);
101: }
102:
103: break;
104: }
105:
106: SchemaType schemaType = cursor.getObject()
107: .schemaType();
108:
109: if (AttachmentUtils.isSwaRefType(schemaType)) {
110: String textContent = cursor.getTextValue();
111: if (textContent.startsWith("cid:")) {
112: textContent = textContent.substring(4);
113:
114: try {
115: // is the textcontent already a URI?
116: new URI(textContent);
117: contentIds
118: .put(textContent, textContent);
119: } catch (RuntimeException e) {
120: // not a URI.. try to create one..
121: String contentId = textContent
122: + "@soapui.org";
123: cursor.setTextValue("cid:" + contentId);
124: contentIds.put(textContent, contentId);
125: }
126: }
127: } else if (AttachmentUtils.isXopInclude(schemaType)) {
128: String contentId = cursor
129: .getAttributeText(new QName("href"));
130: if (contentId != null && contentId.length() > 0) {
131: contentIds.put(contentId, contentId);
132: isXop = true;
133:
134: Attachment[] attachments = container
135: .getAttachmentsForPart(contentId);
136: if (attachments.length == 1) {
137: XmlCursor cur = cursor.newCursor();
138: if (cur.toParent()) {
139: String contentType = getXmlMimeContentType(cur);
140: if (contentType != null
141: && contentType.length() > 0)
142: attachments[0]
143: .setContentType(contentType);
144: }
145:
146: cur.dispose();
147: }
148: }
149: } else if (SchemaUtils.isBinaryType(schemaType)) {
150: String xmimeContentType = getXmlMimeContentType(cursor);
151:
152: // extract contentId
153: String textContent = cursor.getTextValue();
154: Attachment attachment = null;
155: boolean isXopAttachment = false;
156:
157: // is content a reference to a file?
158: if (textContent.startsWith("file:")) {
159: String filename = textContent.substring(5);
160: if (xmimeContentType == null) {
161: inlineData(cursor, schemaType,
162: new FileInputStream(filename));
163: } else if (container.isMtomEnabled()) {
164: MimeBodyPart part = new PreencodedMimeBodyPart(
165: "binary");
166:
167: part.setDataHandler(new DataHandler(
168: new XOPPartDataSource(new File(
169: filename),
170: xmimeContentType,
171: 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: textContent = textContent.substring(4);
181:
182: Attachment[] attachments = container
183: .getAttachmentsForPart(textContent);
184: if (attachments.length == 1) {
185: attachment = attachments[0];
186: } else if (attachments.length > 1) {
187: attachment = buildMulitpartAttachment(attachments);
188: }
189:
190: isXopAttachment = xmimeContentType != null;
191: contentIds.put(textContent, textContent);
192: }
193: // content should be binary data; is this an XOP element which should be serialized with MTOM?
194: else if (container.isMtomEnabled()
195: && xmimeContentType != null) {
196: MimeBodyPart part = new PreencodedMimeBodyPart(
197: "binary");
198:
199: part.setDataHandler(new DataHandler(
200: new XOPPartDataSource(textContent,
201: xmimeContentType,
202: schemaType)));
203:
204: textContent = "http://www.soapui.org/"
205: + System.nanoTime();
206:
207: part.setContentID("<" + textContent + ">");
208: mp.addBodyPart(part);
209:
210: isXopAttachment = true;
211: }
212:
213: // add XOP include?
214: if (isXopAttachment
215: && container.isMtomEnabled()) {
216: buildXopInclude(cursor, textContent);
217: isXop = true;
218: }
219: // inline?
220: else if (attachment != null) {
221: inlineAttachment(cursor, schemaType,
222: attachment);
223: }
224: }
225: }
226:
227: cursor.toNextToken();
228: }
229: } finally {
230: cursor.dispose();
231: }
232:
233: return isXop;
234: }
235:
236: private static void inlineAttachment(XmlCursor cursor,
237: SchemaType schemaType, Attachment attachment)
238: throws Exception {
239: inlineData(cursor, schemaType, attachment.getInputStream());
240: }
241:
242: private static void inlineData(XmlCursor cursor,
243: SchemaType schemaType, InputStream in) throws IOException {
244: String content = null;
245: byte[] data = Tools.readAll(in, -1).toByteArray();
246:
247: if (SchemaUtils.isInstanceOf(schemaType, XmlHexBinary.type)) {
248: content = new String(Hex.encodeHex(data));
249: } else if (SchemaUtils.isInstanceOf(schemaType,
250: XmlBase64Binary.type)) {
251: content = new String(Base64.encodeBase64(data));
252: }
253:
254: XmlCursor c = cursor.newCursor();
255: c.setTextValue(content);
256: c.dispose();
257: }
258:
259: private static void buildXopInclude(XmlCursor cursor,
260: String contentId) {
261: // build xop:Include
262: XmlCursor c = cursor.newCursor();
263: c.removeXmlContents();
264: c.toFirstContentToken();
265: c.beginElement(XOP_INCLUDE_QNAME);
266: c.insertAttributeWithValue(XOP_HREF_QNAME, "cid:" + contentId);
267: c.toNextSibling();
268: c.removeXml();
269: c.dispose();
270: }
271:
272: private static Attachment buildMulitpartAttachment(
273: Attachment[] attachments) {
274: System.out
275: .println("buildMulitpartAttachment(Attachment[] attachments) not implemented!");
276: return null;
277: }
278:
279: public static String buildRootPartContentType(String action,
280: SoapVersion soapVersion) {
281: return "application/xop+xml; charset=UTF-8; type=\""
282: + soapVersion.getContentType() + "; action=\\\""
283: + action + "\\\"\"";
284: }
285:
286: public static String buildMTOMContentType(String header,
287: String action, SoapVersion soapVersion) {
288: if (action == null)
289: action = "";
290:
291: int ix = header.indexOf("boundary");
292: return "multipart/related; type=\"application/xop+xml\"; start=\""
293: + ROOTPART_SOAPUI_ORG
294: + "\"; "
295: + "start-info=\""
296: + soapVersion.getContentType()
297: + "; action=\\\""
298: + action + "\\\"\"; " + header.substring(ix);
299: }
300:
301: public static boolean isSwaRefType(SchemaType schemaType) {
302: return schemaType.getName() != null
303: && schemaType.getName().equals(SWAREF_QNAME);
304: }
305:
306: public static String getXmlMimeContentType(XmlCursor cursor) {
307: String attributeText = cursor
308: .getAttributeText(XMLMIME_CONTENTTYPE_200411);
309: if (attributeText == null)
310: attributeText = cursor
311: .getAttributeText(XMLMIME_CONTENTTYPE_200505);
312: return attributeText;
313: }
314:
315: public static AttachmentEncoding getAttachmentEncoding(
316: WsdlOperation operation, String partName, boolean isResponse) {
317: // make sure we have access
318: if (operation == null
319: || operation.getBindingOperation() == null
320: || operation.getBindingOperation().getOperation() == null) {
321: return AttachmentEncoding.NONE;
322: }
323:
324: javax.wsdl.Part part = null;
325:
326: if (isResponse) {
327: Output output = operation.getBindingOperation()
328: .getOperation().getOutput();
329: if (output == null || output.getMessage() == null) {
330: return AttachmentEncoding.NONE;
331: } else {
332: part = output.getMessage().getPart(partName);
333: }
334: } else {
335: Input input = operation.getBindingOperation()
336: .getOperation().getInput();
337: if (input == null || input.getMessage() == null) {
338: return AttachmentEncoding.NONE;
339: } else {
340: part = input.getMessage().getPart(partName);
341: }
342: }
343:
344: if (part != null) {
345: QName typeName = part.getTypeName();
346: if (typeName.getNamespaceURI().equals(
347: "http://www.w3.org/2001/XMLSchema")) {
348: if (typeName.getLocalPart().equals("base64Binary")) {
349: return AttachmentEncoding.BASE64;
350: } else if (typeName.getLocalPart().equals("hexBinary")) {
351: return AttachmentEncoding.HEX;
352: }
353: }
354: }
355:
356: return AttachmentEncoding.NONE;
357: }
358:
359: public static boolean isXopInclude(SchemaType schemaType) {
360: return XOP_INCLUDE_QNAME.equals(schemaType.getName());
361: }
362:
363: public static List<WsdlAttachmentPart> extractAttachmentParts(
364: WsdlOperation operation, String messageContent,
365: boolean addAnonymous, boolean isResponse) {
366: List<WsdlAttachmentPart> result = new ArrayList<WsdlAttachmentPart>();
367:
368: PartsConfig messageParts = isResponse ? operation.getConfig()
369: .getResponseParts() : operation.getConfig()
370: .getRequestParts();
371: if (messageParts != null) {
372: for (Part part : messageParts.getPartList()) {
373: WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart(
374: part.getName(), part.getContentTypeList());
375: attachmentPart.setType(Attachment.AttachmentType.MIME);
376: result.add(attachmentPart);
377: }
378: }
379:
380: if (messageContent.length() > 0) {
381: WsdlContext wsdlContext = ((WsdlInterface) operation
382: .getInterface()).getWsdlContext();
383: WsdlValidator validator = new WsdlValidator(wsdlContext);
384: try {
385: XmlObject[] requestDocuments = validator
386: .getMessageParts(messageContent, operation
387: .getName(), isResponse);
388:
389: for (XmlObject partDoc : requestDocuments) {
390: XmlCursor cursor = partDoc.newCursor();
391: while (!cursor.isEnddoc()) {
392: if (cursor.isContainer()) {
393: SchemaType schemaType = cursor.getObject()
394: .schemaType();
395: if (schemaType != null) {
396: String attributeText = AttachmentUtils
397: .getXmlMimeContentType(cursor);
398:
399: // xop?
400: if (SchemaUtils
401: .isBinaryType(schemaType)) {
402: String contentId = cursor
403: .getTextValue();
404: if (contentId.startsWith("cid:")) {
405: WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart(
406: contentId.substring(4),
407: attributeText);
408: attachmentPart
409: .setType(attributeText == null ? Attachment.AttachmentType.CONTENT
410: : Attachment.AttachmentType.XOP);
411: result.add(attachmentPart);
412: }
413: } else if (AttachmentUtils
414: .isXopInclude(schemaType)) {
415: String contentId = cursor
416: .getAttributeText(new QName(
417: "href"));
418: if (contentId != null
419: && contentId.length() > 0) {
420: WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart(
421: contentId,
422: attributeText);
423: attachmentPart
424: .setType(Attachment.AttachmentType.XOP);
425: result.add(attachmentPart);
426: }
427: }
428: // swaref?
429: else if (AttachmentUtils
430: .isSwaRefType(schemaType)) {
431: String contentId = cursor
432: .getTextValue();
433: if (contentId.startsWith("cid:")) {
434: WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart(
435: contentId.substring(4),
436: attributeText);
437: attachmentPart
438: .setType(Attachment.AttachmentType.SWAREF);
439: result.add(attachmentPart);
440: }
441: }
442: }
443: }
444:
445: cursor.toNextToken();
446: }
447: }
448: } catch (Exception e) {
449: log.warn(e.getMessage());
450: }
451: }
452:
453: if (addAnonymous)
454: result.add(new WsdlAttachmentPart());
455:
456: return result;
457: }
458:
459: /**
460: * Adds defined attachments as mimeparts
461: */
462:
463: public static void addMimeParts(AttachmentContainer container,
464: MimeMultipart mp, StringToStringMap contentIds)
465: throws MessagingException {
466: // no multipart handling?
467: if (!container.isMultipartEnabled()) {
468: for (int c = 0; c < container.getAttachmentCount(); c++) {
469: Attachment att = container.getAttachmentAt(c);
470: addSingleAttachment(mp, contentIds, att);
471: }
472: } else {
473: // first identify if any part has more than one attachments
474: Map<String, List<Attachment>> attachmentsMap = new HashMap<String, List<Attachment>>();
475: for (int c = 0; c < container.getAttachmentCount(); c++) {
476: Attachment att = container.getAttachmentAt(c);
477: String partName = att.getPart();
478:
479: if (!attachmentsMap.containsKey(partName)) {
480: attachmentsMap.put(partName,
481: new ArrayList<Attachment>());
482: }
483:
484: attachmentsMap.get(partName).add(att);
485: }
486:
487: // add attachments
488: for (Iterator<String> i = attachmentsMap.keySet()
489: .iterator(); i.hasNext();) {
490: List<Attachment> attachments = attachmentsMap.get(i
491: .next());
492: if (attachments.size() == 1) {
493: Attachment att = attachments.get(0);
494: addSingleAttachment(mp, contentIds, att);
495: }
496: // more than one attachment with the same part -> create multipart attachment
497: else if (attachments.size() > 1) {
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,
509: StringToStringMap contentIds, List<Attachment> attachments)
510: throws MessagingException {
511: MimeMultipart multipart = new MimeMultipart("mixed");
512: for (int c = 0; c < attachments.size(); c++) {
513: Attachment att = attachments.get(c);
514: String contentType = att.getContentType();
515:
516: MimeBodyPart part = contentType.startsWith("text/") ? new MimeBodyPart()
517: : new PreencodedMimeBodyPart("binary");
518:
519: part.setDataHandler(new DataHandler(
520: new AttachmentDataSource(att)));
521: initPartContentId(contentIds, part, att, false);
522: multipart.addBodyPart(part);
523: }
524:
525: MimeBodyPart part = new PreencodedMimeBodyPart("binary");
526: part.setDataHandler(new DataHandler(
527: new MultipartAttachmentDataSource(multipart)));
528:
529: Attachment attachment = attachments.get(0);
530: initPartContentId(contentIds, part, attachment, true);
531:
532: mp.addBodyPart(part);
533: }
534:
535: public static void initPartContentId(StringToStringMap contentIds,
536: MimeBodyPart part, Attachment attachment,
537: boolean isMultipart) throws MessagingException {
538: String partName = attachment.getPart();
539:
540: String contentID = attachment.getContentID();
541: if (contentID != null) {
542: int ix = contentID.indexOf(' ');
543: if (ix != -1)
544: part.setContentID("<"
545: + (isMultipart ? contentID.substring(ix + 1)
546: : contentID.substring(0, ix)) + ">");
547: else
548: part.setContentID(contentID);
549:
550: } else if (partName != null
551: && !partName.equals(WsdlAttachmentPart.ANONYMOUS_NAME)) {
552: if (contentIds.containsKey(partName)) {
553: part.setContentID("<" + contentIds.get(partName) + ">");
554: } else {
555: part.setContentID("<" + partName + "="
556: + System.nanoTime() + "@soapui.org>");
557: }
558: }
559: }
560:
561: /**
562: * Adds a simple MimeBodyPart from an attachment
563: */
564:
565: public static void addSingleAttachment(MimeMultipart mp,
566: StringToStringMap contentIds, Attachment att)
567: throws MessagingException {
568: String contentType = att.getContentType();
569: MimeBodyPart part = contentType.startsWith("text/") ? new MimeBodyPart()
570: : new PreencodedMimeBodyPart("binary");
571:
572: part.setDataHandler(new DataHandler(new AttachmentDataSource(
573: att)));
574: initPartContentId(contentIds, part, att, false);
575:
576: mp.addBodyPart(part);
577: }
578:
579: public static final Session JAVAMAIL_SESSION = Session
580: .getDefaultInstance(new Properties());
581: }
|