View Javadoc
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       */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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 
194     public int getDataLen() {
195         return dataLen;
196     }
197 
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      */
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      */
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      */
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      */
285     public String toString() {
286         get();  // fill in the buffer
287         return DatatypeConverterImpl._printBase64Binary(data, 0, dataLen);
288     }
289 
290     @Override
291     public void writeTo(char[] buf, int start) {
292         get();
293         DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, buf, start);
294     }
295 
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 
302     public void writeTo(XMLStreamWriter output) throws IOException, XMLStreamException {
303         get();
304         DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, output);
305     }
306 
307 }