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