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