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 26 package com.sun.xml.internal.org.jvnet.staxex; 27 28 import javax.activation.DataHandler; 29 import javax.activation.DataSource; 30 import java.io.ByteArrayInputStream; 31 import java.io.ByteArrayOutputStream; 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import javax.xml.stream.XMLStreamException; 38 import javax.xml.stream.XMLStreamWriter; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 42 // for testing method 43 //import com.sun.xml.internal.stream.writers.XMLStreamWriterImpl; 44 //import java.io.FileNotFoundException; 45 //import java.io.FileWriter; 46 //import javax.activation.FileDataSource; 47 48 /** 49 * Binary data represented as base64-encoded string 50 * in XML. 51 * 52 * <p> 53 * Used in conjunction with {@link XMLStreamReaderEx} 54 * and {@link XMLStreamWriterEx}. 55 * 56 * @author Kohsuke Kawaguchi, Martin Grebac 57 */ 58 public class Base64Data implements CharSequence, Cloneable { 59 60 // either dataHandler or (data,dataLen,mimeType?) must be present 61 // (note that having both is allowed) 62 63 private DataHandler dataHandler; 64 private byte[] data; 65 private String hrefCid; 66 67 /** 68 * Length of the valid data in {@link #data}. 69 */ 70 private int dataLen; 71 /** 72 * True if {@link #data} can be cloned by reference 73 * if Base64Data instance is cloned. 74 */ 75 private boolean dataCloneByRef; 76 /** 77 * Optional MIME type of {@link #data}. 78 * 79 * Unused when {@link #dataHandler} is set. 80 * Use {@link DataHandler#getContentType()} in that case. 81 */ 82 private String mimeType; 83 84 /** 85 * Default constructor 86 */ 87 public Base64Data() { 88 } 89 90 private static final Logger logger = Logger.getLogger(Base64Data.class.getName()); 91 92 /** 93 * Clone constructor 94 * @param that needs to be cloned 95 */ 96 public Base64Data(Base64Data that) { 97 that.get(); 98 if (that.dataCloneByRef) { 99 this.data = that.data; 100 } else { 101 this.data = new byte[that.dataLen]; 102 System.arraycopy(that.data, 0, this.data, 0, that.dataLen); 103 } 104 105 this.dataCloneByRef = true; 106 this.dataLen = that.dataLen; 107 this.dataHandler = null; 108 this.mimeType = that.mimeType; 109 } 110 111 /** 112 * Fills in the data object by a portion of the byte[]. 113 * 114 * @param data actual data 115 * @param len 116 * data[0] to data[len-1] are treated as the data. 117 * @param mimeType MIME type 118 * @param cloneByRef 119 * true if data[] can be cloned by reference 120 */ 121 public void set(byte[] data, int len, String mimeType, boolean cloneByRef) { 122 this.data = data; 123 this.dataLen = len; 124 this.dataCloneByRef = cloneByRef; 125 this.dataHandler = null; 126 this.mimeType = mimeType; 127 } 128 129 /** 130 * Fills in the data object by a portion of the byte[]. 131 * 132 * @param data actual data bytes 133 * @param len 134 * data[0] to data[len-1] are treated as the data. 135 * @param mimeType MIME type 136 */ 137 public void set(byte[] data, int len, String mimeType) { 138 set(data,len,mimeType,false); 139 } 140 141 /** 142 * Fills in the data object by the byte[] of the exact length. 143 * 144 * @param data 145 * this buffer may be owned directly by the unmarshaleld JAXB object. 146 * @param mimeType MIME type 147 */ 148 public void set(byte[] data,String mimeType) { 149 set(data,data.length,mimeType,false); 150 } 151 152 /** 153 * Fills in the data object by a {@link DataHandler}. 154 * 155 * @param data DataHandler for the data 156 */ 157 public void set(DataHandler data) { 158 assert data!=null; 159 this.dataHandler = data; 160 this.data = null; 161 } 162 163 /** 164 * Gets the raw data. If the returned DataHandler is {@link StreamingDataHandler}, 165 * callees may need to downcast to take advantage of its capabilities. 166 * 167 * @see StreamingDataHandler 168 * @return DataHandler for the data 169 */ 170 public DataHandler getDataHandler() { 171 if(dataHandler==null){ 172 dataHandler = new Base64StreamingDataHandler(new Base64DataSource()); 173 } else if (!(dataHandler instanceof StreamingDataHandler)) { 174 dataHandler = new FilterDataHandler(dataHandler); 175 } 176 return dataHandler; 177 } 178 179 private final class Base64DataSource implements DataSource { 180 public String getContentType() { 181 return getMimeType(); 182 } 183 184 public InputStream getInputStream() { 185 return new ByteArrayInputStream(data,0,dataLen); 186 } 187 188 public String getName() { 189 return null; 190 } 191 192 public OutputStream getOutputStream() { 193 throw new UnsupportedOperationException(); 194 } 195 196 } 197 198 private final class Base64StreamingDataHandler extends StreamingDataHandler { 199 200 Base64StreamingDataHandler(DataSource source) { 201 super(source); 202 } 203 204 public InputStream readOnce() throws IOException { 205 return getDataSource().getInputStream(); 206 } 207 208 public void moveTo(File dst) throws IOException { 209 FileOutputStream fout = new FileOutputStream(dst); 210 try { 211 fout.write(data, 0, dataLen); 212 } finally { 213 fout.close(); 214 } 215 } 216 217 public void close() throws IOException { 218 // nothing to do 219 } 220 } 221 222 private static final class FilterDataHandler extends StreamingDataHandler { 223 224 FilterDataHandler(DataHandler dh) { 225 super(dh.getDataSource()); 226 } 227 228 public InputStream readOnce() throws IOException { 229 return getDataSource().getInputStream(); 230 } 231 232 public void moveTo(File dst) throws IOException { 233 byte[] buf = new byte[8192]; 234 InputStream in = null; 235 OutputStream out = null; 236 try { 237 in = getDataSource().getInputStream(); 238 out = new FileOutputStream(dst); 239 while (true) { 240 int amountRead = in.read(buf); 241 if (amountRead == -1) { 242 break; 243 } 244 out.write(buf, 0, amountRead); 245 } 246 } finally { 247 if (in != null) { 248 try { 249 in.close(); 250 } catch(IOException ioe) { 251 // nothing to do 252 } 253 } 254 if (out != null) { 255 try { 256 out.close(); 257 } catch(IOException ioe) { 258 // nothing to do 259 } 260 } 261 } 262 } 263 264 public void close() throws IOException { 265 // nothing to do 266 } 267 } 268 269 /** 270 * Gets the byte[] of the exact length. 271 * 272 * @return byte[] for data 273 */ 274 public byte[] getExact() { 275 get(); 276 if(dataLen!=data.length) { 277 byte[] buf = new byte[dataLen]; 278 System.arraycopy(data,0,buf,0,dataLen); 279 data = buf; 280 } 281 return data; 282 } 283 284 /** 285 * Gets the data as an {@link InputStream}. 286 * 287 * @return data as InputStream 288 * @throws IOException if i/o error occurs 289 */ 290 public InputStream getInputStream() throws IOException { 291 if(dataHandler!=null) { 292 return dataHandler.getInputStream(); 293 } else { 294 return new ByteArrayInputStream(data,0,dataLen); 295 } 296 } 297 298 /** 299 * Returns false if this object only has {@link DataHandler} and therefore 300 * {@link #get()} operation is likely going to be expensive. 301 * 302 * @return false if it has only DataHandler 303 */ 304 public boolean hasData() { 305 return data!=null; 306 } 307 308 /** 309 * Gets the raw data. The size of the byte array maybe larger than the actual length. 310 * 311 * @return data as byte[], the array may be larger 312 */ 313 public byte[] get() { 314 if(data==null) { 315 try { 316 ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024); 317 InputStream is = dataHandler.getDataSource().getInputStream(); 318 baos.readFrom(is); 319 is.close(); 320 data = baos.getBuffer(); 321 dataLen = baos.size(); 322 dataCloneByRef = true; 323 } catch (IOException e) { 324 // TODO: report the error to the unmarshaller 325 dataLen = 0; // recover by assuming length-0 data 326 } 327 } 328 return data; 329 } 330 331 /** 332 * Gets the length of the binary data counted in bytes. 333 * 334 * Note that if this object encapsulates {@link DataHandler}, 335 * this method would have to read the whole thing into {@code byte[]} 336 * just to count the length, because {@link DataHandler} 337 * doesn't easily expose the length. 338 * 339 * @return no of bytes 340 */ 341 public int getDataLen() { 342 get(); 343 return dataLen; 344 } 345 346 public String getMimeType() { 347 if (mimeType==null) { 348 return "application/octet-stream"; 349 } 350 return mimeType; 351 } 352 353 /** 354 * Gets the number of characters needed to represent 355 * this binary data in the base64 encoding. 356 */ 357 public int length() { 358 // for each 3 bytes you use 4 chars 359 // if the remainder is 1 or 2 there will be 4 more 360 get(); // fill in the buffer if necessary 361 return ((dataLen+2)/3)*4; 362 } 363 364 /** 365 * Encode this binary data in the base64 encoding 366 * and returns the character at the specified position. 367 */ 368 public char charAt(int index) { 369 // we assume that the length() method is called before this method 370 // (otherwise how would the caller know that the index is valid?) 371 // so we assume that the byte[] is already populated 372 373 int offset = index%4; 374 int base = (index/4)*3; 375 376 byte b1,b2; 377 378 switch(offset) { 379 case 0: 380 return Base64Encoder.encode(data[base]>>2); 381 case 1: 382 if (base+1<dataLen) { 383 b1 = data[base+1]; 384 } else { 385 b1 = 0; 386 } 387 return Base64Encoder.encode( 388 ((data[base]&0x3)<<4) | 389 ((b1>>4)&0xF)); 390 case 2: 391 if (base+1<dataLen) { 392 b1 = data[base+1]; 393 if (base+2<dataLen) { 394 b2 = data[base+2]; 395 } else { 396 b2 = 0; 397 } 398 399 return Base64Encoder.encode( 400 ((b1&0xF)<<2)| 401 ((b2>>6)&0x3)); 402 } else { 403 return '='; 404 } 405 case 3: 406 if(base+2<dataLen) { 407 return Base64Encoder.encode(data[base+2]&0x3F); 408 } else { 409 return '='; 410 } 411 } 412 413 throw new IllegalStateException(); 414 } 415 416 /** 417 * Internally this is only used to split a text to a list, 418 * which doesn't happen that much for base64. 419 * So this method should be smaller than faster. 420 */ 421 public CharSequence subSequence(int start, int end) { 422 StringBuilder buf = new StringBuilder(); 423 get(); // fill in the buffer if we haven't done so 424 for (int i=start; i<end; i++ ) { 425 buf.append(charAt(i)); 426 } 427 return buf; 428 } 429 430 /** 431 * Returns the base64 encoded string of this data. 432 */ 433 @Override 434 public String toString() { 435 get(); // fill in the buffer 436 return Base64Encoder.print(data, 0, dataLen); 437 } 438 439 public void writeTo(char[] buf, int start) { 440 get(); 441 Base64Encoder.print(data, 0, dataLen, buf, start); 442 } 443 444 private static final int CHUNK_SIZE; 445 static { 446 int bufSize = 1024; 447 try { 448 String bufSizeStr = getProperty("com.sun.xml.internal.org.jvnet.staxex.Base64DataStreamWriteBufferSize"); 449 if (bufSizeStr != null) { 450 bufSize = Integer.parseInt(bufSizeStr); 451 } 452 } catch (Exception e) { 453 logger.log(Level.INFO, "Error reading com.sun.xml.internal.org.jvnet.staxex.Base64DataStreamWriteBufferSize property", e); 454 } 455 CHUNK_SIZE = bufSize; 456 } 457 458 public void writeTo(XMLStreamWriter output) throws IOException, XMLStreamException { 459 if (data==null) { 460 try { 461 InputStream is = dataHandler.getDataSource().getInputStream(); 462 ByteArrayOutputStream outStream = new ByteArrayOutputStream(); // dev-null stream 463 Base64EncoderStream encWriter = new Base64EncoderStream(output, outStream); 464 int b; 465 byte[] buffer = new byte[CHUNK_SIZE]; 466 while ((b = is.read(buffer)) != -1) { 467 encWriter.write(buffer, 0, b); 468 } 469 outStream.close(); 470 encWriter.close(); 471 } catch (IOException e) { 472 dataLen = 0; // recover by assuming length-0 data 473 throw e; 474 } 475 } else { 476 // the data is already in memory and not streamed 477 String s = Base64Encoder.print(data, 0, dataLen); 478 output.writeCharacters(s); 479 } 480 } 481 482 @Override 483 public Base64Data clone() { 484 try { 485 Base64Data clone = (Base64Data) super.clone(); 486 clone.get(); 487 if (clone.dataCloneByRef) { 488 this.data = clone.data; 489 } else { 490 this.data = new byte[clone.dataLen]; 491 System.arraycopy(clone.data, 0, this.data, 0, clone.dataLen); 492 } 493 494 this.dataCloneByRef = true; 495 this.dataLen = clone.dataLen; 496 this.dataHandler = null; 497 this.mimeType = clone.mimeType; 498 return clone; 499 } catch (CloneNotSupportedException ex) { 500 Logger.getLogger(Base64Data.class.getName()).log(Level.SEVERE, null, ex); 501 return null; 502 } 503 } 504 505 static String getProperty(final String propName) { 506 if (System.getSecurityManager() == null) { 507 return System.getProperty(propName); 508 } else { 509 return (String) java.security.AccessController.doPrivileged( 510 new java.security.PrivilegedAction() { 511 public java.lang.Object run() { 512 return System.getProperty(propName); 513 } 514 }); 515 } 516 } 517 518 // public static void main(String[] args) throws FileNotFoundException, IOException, XMLStreamException { 519 // Base64Data data = new Base64Data(); 520 // 521 // File f = new File("/Users/snajper/work/builds/weblogic/wls1211_dev.zip"); 522 // FileDataSource fds = new FileDataSource(f); 523 // DataHandler dh = new DataHandler(fds); 524 // data.set(dh); 525 // 526 // FileWriter fw = new FileWriter(new File("/Users/snajper/Desktop/b.txt")); 527 // XMLStreamWriterImpl wi = new XMLStreamWriterImpl(fw, null); 528 // 529 // data.writeTo(wi); 530 // wi.flush();fw.flush(); 531 // //System.out.println("SW: " + sw.toString()); 532 // 533 // } 534 535 public String getHrefCid() { 536 if (hrefCid == null && dataHandler != null && dataHandler instanceof StreamingDataHandler) { 537 hrefCid = ((StreamingDataHandler)dataHandler).getHrefCid(); 538 } 539 return hrefCid; 540 } 541 542 public void setHrefCid(final String cid) { 543 this.hrefCid = cid; 544 if (dataHandler != null && dataHandler instanceof StreamingDataHandler) ((StreamingDataHandler)dataHandler).setHrefCid(cid); 545 } 546 547 }