1/* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.xml.internal.ws.encoding; 27 28import com.sun.xml.internal.ws.api.SOAPVersion; 29import com.sun.xml.internal.ws.api.WSFeatureList; 30import com.sun.xml.internal.ws.api.message.Attachment; 31import com.sun.xml.internal.ws.api.message.AttachmentEx; 32import com.sun.xml.internal.ws.api.message.Message; 33import com.sun.xml.internal.ws.api.message.Packet; 34import com.sun.xml.internal.ws.api.pipe.Codec; 35import com.sun.xml.internal.ws.api.pipe.ContentType; 36import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature; 37 38import java.io.IOException; 39import java.io.InputStream; 40import java.io.OutputStream; 41import java.nio.channels.ReadableByteChannel; 42import java.util.Iterator; 43import java.util.UUID; 44 45/** 46 * {@link Codec}s that uses the MIME multipart as the underlying format. 47 * 48 * <p> 49 * When the runtime needs to dynamically choose a {@link Codec}, and 50 * when there are more than one {@link Codec}s that use MIME multipart, 51 * it is often impossible to determine the right {@link Codec} unless 52 * you parse the multipart message to some extent. 53 * 54 * <p> 55 * By having all such {@link Codec}s extending from this class, 56 * the "sniffer" can decode a multipart message partially, and then 57 * pass the partial parse result to the ultimately-responsible {@link Codec}. 58 * This improves the performance. 59 * 60 * @author Kohsuke Kawaguchi 61 */ 62abstract class MimeCodec implements Codec { 63 64 public static final String MULTIPART_RELATED_MIME_TYPE = "multipart/related"; 65 66 protected Codec mimeRootCodec; 67 protected final SOAPVersion version; 68 protected final WSFeatureList features; 69 70 protected MimeCodec(SOAPVersion version, WSFeatureList f) { 71 this.version = version; 72 this.features = f; 73 } 74 75 public String getMimeType() { 76 return MULTIPART_RELATED_MIME_TYPE; 77 } 78 79 protected Codec getMimeRootCodec(Packet packet) { 80 return mimeRootCodec; 81 } 82 83 // TODO: preencode String literals to byte[] so that they don't have to 84 // go through char[]->byte[] conversion at runtime. 85 public ContentType encode(Packet packet, OutputStream out) throws IOException { 86 Message msg = packet.getMessage(); 87 if (msg == null) { 88 return null; 89 } 90 ContentTypeImpl ctImpl = (ContentTypeImpl)getStaticContentType(packet); 91 String boundary = ctImpl.getBoundary(); 92 String rootId = ctImpl.getRootId(); 93 boolean hasAttachments = (boundary != null); 94 Codec rootCodec = getMimeRootCodec(packet); 95 if (hasAttachments) { 96 writeln("--"+boundary, out); 97 ContentType ct = rootCodec.getStaticContentType(packet); 98 String ctStr = (ct != null) ? ct.getContentType() : rootCodec.getMimeType(); 99 if (rootId != null) writeln("Content-ID: " + rootId, out); 100 writeln("Content-Type: " + ctStr, out); 101 writeln(out); 102 } 103 ContentType primaryCt = rootCodec.encode(packet, out); 104 105 if (hasAttachments) { 106 writeln(out); 107 // Encode all the attchments 108 for (Attachment att : msg.getAttachments()) { 109 writeln("--"+boundary, out); 110 //SAAJ's AttachmentPart.getContentId() returns content id already enclosed with 111 //angle brackets. For now put angle bracket only if its not there 112 String cid = att.getContentId(); 113 if(cid != null && cid.length() >0 && cid.charAt(0) != '<') 114 cid = '<' + cid + '>'; 115 writeln("Content-Id:" + cid, out); 116 writeln("Content-Type: " + att.getContentType(), out); 117 writeCustomMimeHeaders(att, out); 118 writeln("Content-Transfer-Encoding: binary", out); 119 writeln(out); // write \r\n 120 att.writeTo(out); 121 writeln(out); // write \r\n 122 } 123 writeAsAscii("--"+boundary, out); 124 writeAsAscii("--", out); 125 } 126 // TODO not returing correct multipart/related type(no boundary) 127 return hasAttachments ? ctImpl : primaryCt; 128 } 129 130 private void writeCustomMimeHeaders(Attachment att, OutputStream out) throws IOException { 131 if (att instanceof AttachmentEx) { 132 Iterator<AttachmentEx.MimeHeader> allMimeHeaders = ((AttachmentEx) att).getMimeHeaders(); 133 while (allMimeHeaders.hasNext()) { 134 AttachmentEx.MimeHeader mh = allMimeHeaders.next(); 135 String name = mh.getName(); 136 137 if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Id".equalsIgnoreCase(name)) { 138 writeln(name +": " + mh.getValue(), out); 139 } 140 } 141 } 142 } 143 144 public ContentType getStaticContentType(Packet packet) { 145 ContentType ct = (ContentType) packet.getInternalContentType(); 146 if ( ct != null ) return ct; 147 Message msg = packet.getMessage(); 148 boolean hasAttachments = !msg.getAttachments().isEmpty(); 149 Codec rootCodec = getMimeRootCodec(packet); 150 151 if (hasAttachments) { 152 String boundary = "uuid:" + UUID.randomUUID().toString(); 153 String boundaryParameter = "boundary=\"" + boundary + "\""; 154 // TODO use primaryEncoder to get type 155 String messageContentType = MULTIPART_RELATED_MIME_TYPE + 156 "; type=\"" + rootCodec.getMimeType() + "\"; " + 157 boundaryParameter; 158 ContentTypeImpl impl = new ContentTypeImpl(messageContentType, packet.soapAction, null); 159 impl.setBoundary(boundary); 160 impl.setBoundaryParameter(boundaryParameter); 161 packet.setContentType(impl); 162 return impl; 163 } else { 164 ct = rootCodec.getStaticContentType(packet); 165 packet.setContentType(ct); 166 return ct; 167 } 168 } 169 170 /** 171 * Copy constructor. 172 */ 173 protected MimeCodec(MimeCodec that) { 174 this.version = that.version; 175 this.features = that.features; 176 } 177 178 public void decode(InputStream in, String contentType, Packet packet) throws IOException { 179 MimeMultipartParser parser = new MimeMultipartParser(in, contentType, features.get(StreamingAttachmentFeature.class)); 180 decode(parser,packet); 181 } 182 183 public void decode(ReadableByteChannel in, String contentType, Packet packet) { 184 throw new UnsupportedOperationException(); 185 } 186 187 /** 188 * Parses a {@link Packet} from a {@link MimeMultipartParser}. 189 */ 190 protected abstract void decode(MimeMultipartParser mpp, Packet packet) throws IOException; 191 192 public abstract MimeCodec copy(); 193 194 195 public static void writeln(String s,OutputStream out) throws IOException { 196 writeAsAscii(s,out); 197 writeln(out); 198 } 199 200 /** 201 * Writes a string as ASCII string. 202 */ 203 public static void writeAsAscii(String s,OutputStream out) throws IOException { 204 int len = s.length(); 205 for( int i=0; i<len; i++ ) 206 out.write((byte)s.charAt(i)); 207 } 208 209 public static void writeln(OutputStream out) throws IOException { 210 out.write('\r'); 211 out.write('\n'); 212 } 213} 214