1 /* 2 * Copyright (c) 1997, 2011, 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 26 package com.sun.xml.internal.bind.v2.runtime.unmarshaller; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 33 import javax.activation.DataHandler; 34 import javax.activation.DataSource; 35 import javax.xml.stream.XMLStreamException; 36 37 import javax.xml.stream.XMLStreamWriter; 38 39 import com.sun.xml.internal.bind.DatatypeConverterImpl; 40 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; 41 import com.sun.xml.internal.bind.v2.runtime.output.Pcdata; 42 import com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput; 43 import com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx; 44 import com.sun.istack.internal.Nullable; 45 46 /** 47 * Fed to unmarshaller when the 'text' data is actually 48 * a virtual image of base64 encoding of the binary data 49 * transferred on the wire. 50 * 51 * Used for the MTOM support. 52 * 53 * This object is mutable and the owner of this object can 54 * reuse it with new data. 55 * 56 * Also used by the marshaller to write out the binary data 57 * that could be possibly attached. 58 * 59 * @see XmlVisitor#text(CharSequence) 60 * @see XMLSerializer#text(Pcdata,String) 61 * 62 * @author Kohsuke Kawaguchi, Martin Grebac 63 */ 64 public final class Base64Data extends Pcdata { 65 66 // either dataHandler or (data,dataLen,mimeType?) must be present 67 private DataHandler dataHandler; 68 private byte[] data; 69 /** 70 * Length of the valid data in {@link #data}. 71 */ 72 private int dataLen; 73 /** 74 * Optional MIME type of {@link #data}. 75 * 76 * Unused when {@link #dataHandler} is set. 77 * Use {@link DataHandler#getContentType()} in that case. 78 */ 79 private @Nullable 80 String mimeType; 81 82 /** 83 * Fills in the data object by a portion of the byte[]. 84 * 85 * @param len 86 * data[0] to data[len-1] are treated as the data. 87 */ set(byte[] data, int len, @Nullable String mimeType)88 public void set(byte[] data, int len, @Nullable String mimeType) { 89 this.data = data; 90 this.dataLen = len; 91 this.dataHandler = null; 92 this.mimeType = mimeType; 93 } 94 95 /** 96 * Fills in the data object by the byte[] of the exact length. 97 * 98 * @param data 99 * this buffer may be owned directly by the unmarshaleld JAXB object. 100 */ set(byte[] data, @Nullable String mimeType)101 public void set(byte[] data, @Nullable String mimeType) { 102 set(data, data.length, mimeType); 103 } 104 105 /** 106 * Fills in the data object by a {@link DataHandler}. 107 */ set(DataHandler data)108 public void set(DataHandler data) { 109 assert data != null; 110 this.dataHandler = data; 111 this.data = null; 112 } 113 114 /** 115 * Gets the raw data. 116 */ getDataHandler()117 public DataHandler getDataHandler() { 118 if (dataHandler == null) { 119 dataHandler = new DataHandler(new DataSource() { 120 121 public String getContentType() { 122 return getMimeType(); 123 } 124 125 public InputStream getInputStream() { 126 return new ByteArrayInputStream(data, 0, dataLen); 127 } 128 129 public String getName() { 130 return null; 131 } 132 133 public OutputStream getOutputStream() { 134 throw new UnsupportedOperationException(); 135 } 136 }); 137 } 138 139 return dataHandler; 140 } 141 142 /** 143 * Gets the byte[] of the exact length. 144 */ getExact()145 public byte[] getExact() { 146 get(); 147 if (dataLen != data.length) { 148 byte[] buf = new byte[dataLen]; 149 System.arraycopy(data, 0, buf, 0, dataLen); 150 data = buf; 151 } 152 return data; 153 } 154 155 /** 156 * Gets the data as an {@link InputStream}. 157 */ getInputStream()158 public InputStream getInputStream() throws IOException { 159 if (dataHandler != null) { 160 return dataHandler.getInputStream(); 161 } else { 162 return new ByteArrayInputStream(data, 0, dataLen); 163 } 164 } 165 166 /** 167 * Returns false if this object only has {@link DataHandler} and therefore 168 * {@link #get()} operation is likely going to be expensive. 169 */ hasData()170 public boolean hasData() { 171 return data != null; 172 } 173 174 /** 175 * Gets the raw data. The size of the byte array maybe larger than the actual length. 176 */ get()177 public byte[] get() { 178 if (data == null) { 179 try { 180 ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024); 181 InputStream is = dataHandler.getDataSource().getInputStream(); 182 baos.readFrom(is); 183 is.close(); 184 data = baos.getBuffer(); 185 dataLen = baos.size(); 186 } catch (IOException e) { 187 // TODO: report the error to the unmarshaller 188 dataLen = 0; // recover by assuming length-0 data 189 } 190 } 191 return data; 192 } 193 getDataLen()194 public int getDataLen() { 195 return dataLen; 196 } 197 getMimeType()198 public String getMimeType() { 199 if (mimeType == null) { 200 return "application/octet-stream"; 201 } 202 return mimeType; 203 } 204 205 /** 206 * Gets the number of characters needed to represent 207 * this binary data in the base64 encoding. 208 */ length()209 public int length() { 210 // for each 3 bytes you use 4 chars 211 // if the remainder is 1 or 2 there will be 4 more 212 get(); // fill in the buffer if necessary 213 return ((dataLen + 2) / 3) * 4; 214 } 215 216 /** 217 * Encode this binary data in the base64 encoding 218 * and returns the character at the specified position. 219 */ charAt(int index)220 public char charAt(int index) { 221 // we assume that the length() method is called before this method 222 // (otherwise how would the caller know that the index is valid?) 223 // so we assume that the byte[] is already populated 224 225 int offset = index % 4; 226 int base = (index / 4) * 3; 227 228 byte b1, b2; 229 230 switch (offset) { 231 case 0: 232 return DatatypeConverterImpl.encode(data[base] >> 2); 233 case 1: 234 if (base + 1 < dataLen) { 235 b1 = data[base + 1]; 236 } else { 237 b1 = 0; 238 } 239 return DatatypeConverterImpl.encode( 240 ((data[base] & 0x3) << 4) 241 | ((b1 >> 4) & 0xF)); 242 case 2: 243 if (base + 1 < dataLen) { 244 b1 = data[base + 1]; 245 if (base + 2 < dataLen) { 246 b2 = data[base + 2]; 247 } else { 248 b2 = 0; 249 } 250 251 return DatatypeConverterImpl.encode( 252 ((b1 & 0xF) << 2) 253 | ((b2 >> 6) & 0x3)); 254 } else { 255 return '='; 256 } 257 case 3: 258 if (base + 2 < dataLen) { 259 return DatatypeConverterImpl.encode(data[base + 2] & 0x3F); 260 } else { 261 return '='; 262 } 263 } 264 265 throw new IllegalStateException(); 266 } 267 268 /** 269 * Internally this is only used to split a text to a list, 270 * which doesn't happen that much for base64. 271 * So this method should be smaller than faster. 272 */ subSequence(int start, int end)273 public CharSequence subSequence(int start, int end) { 274 StringBuilder buf = new StringBuilder(); 275 get(); // fill in the buffer if we haven't done so 276 for (int i = start; i < end; i++) { 277 buf.append(charAt(i)); 278 } 279 return buf; 280 } 281 282 /** 283 * Returns the base64 encoded string of this data. 284 */ toString()285 public String toString() { 286 get(); // fill in the buffer 287 return DatatypeConverterImpl._printBase64Binary(data, 0, dataLen); 288 } 289 290 @Override writeTo(char[] buf, int start)291 public void writeTo(char[] buf, int start) { 292 get(); 293 DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, buf, start); 294 } 295 writeTo(UTF8XmlOutput output)296 public void writeTo(UTF8XmlOutput output) throws IOException { 297 // TODO: this is inefficient if the data source is note byte[] but DataHandler 298 get(); 299 output.text(data, dataLen); 300 } 301 writeTo(XMLStreamWriter output)302 public void writeTo(XMLStreamWriter output) throws IOException, XMLStreamException { 303 get(); 304 DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, output); 305 } 306 307 } 308