001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.bind.v2.runtime.unmarshaller;
038:
039: import java.io.ByteArrayInputStream;
040: import java.io.IOException;
041: import java.io.InputStream;
042: import java.io.OutputStream;
043:
044: import javax.activation.DataHandler;
045: import javax.activation.DataSource;
046:
047: import com.sun.xml.bind.DatatypeConverterImpl;
048: import com.sun.xml.bind.v2.runtime.XMLSerializer;
049: import com.sun.xml.bind.v2.runtime.output.Pcdata;
050: import com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput;
051: import com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx;
052: import com.sun.istack.Nullable;
053:
054: /**
055: * Fed to unmarshaller when the 'text' data is actually
056: * a virtual image of base64 encoding of the binary data
057: * transferred on the wire.
058: *
059: * Used for the MTOM support.
060: *
061: * This object is mutable and the owner of this object can
062: * reuse it with new data.
063: *
064: * Also used by the marshaller to write out the binary data
065: * that could be possibly attached.
066: *
067: * @see XmlVisitor#text(CharSequence)
068: * @see XMLSerializer#text(Pcdata,String)
069: *
070: * @author Kohsuke Kawaguchi
071: */
072: public final class Base64Data extends Pcdata {
073:
074: // either dataHandler or (data,dataLen,mimeType?) must be present
075:
076: private DataHandler dataHandler;
077:
078: private byte[] data;
079: /**
080: * Length of the valid data in {@link #data}.
081: */
082: private int dataLen;
083: /**
084: * Optional MIME type of {@link #data}.
085: *
086: * Unused when {@link #dataHandler} is set.
087: * Use {@link DataHandler#getContentType()} in that case.
088: */
089: private @Nullable
090: String mimeType;
091:
092: /**
093: * Fills in the data object by a portion of the byte[].
094: *
095: * @param len
096: * data[0] to data[len-1] are treated as the data.
097: */
098: public void set(byte[] data, int len, @Nullable
099: String mimeType) {
100: this .data = data;
101: this .dataLen = len;
102: this .dataHandler = null;
103: this .mimeType = mimeType;
104: }
105:
106: /**
107: * Fills in the data object by the byte[] of the exact length.
108: *
109: * @param data
110: * this buffer may be owned directly by the unmarshaleld JAXB object.
111: */
112: public void set(byte[] data, @Nullable
113: String mimeType) {
114: set(data, data.length, mimeType);
115: }
116:
117: /**
118: * Fills in the data object by a {@link DataHandler}.
119: */
120: public void set(DataHandler data) {
121: assert data != null;
122: this .dataHandler = data;
123: this .data = null;
124: }
125:
126: /**
127: * Gets the raw data.
128: */
129: public DataHandler getDataHandler() {
130: if (dataHandler == null) {
131: dataHandler = new DataHandler(new DataSource() {
132: public String getContentType() {
133: return getMimeType();
134: }
135:
136: public InputStream getInputStream() {
137: return new ByteArrayInputStream(data, 0, dataLen);
138: }
139:
140: public String getName() {
141: return null;
142: }
143:
144: public OutputStream getOutputStream() {
145: throw new UnsupportedOperationException();
146: }
147: });
148: }
149:
150: return dataHandler;
151: }
152:
153: /**
154: * Gets the byte[] of the exact length.
155: */
156: public byte[] getExact() {
157: get();
158: if (dataLen != data.length) {
159: byte[] buf = new byte[dataLen];
160: System.arraycopy(data, 0, buf, 0, dataLen);
161: data = buf;
162: }
163: return data;
164: }
165:
166: /**
167: * Gets the data as an {@link InputStream}.
168: */
169: public InputStream getInputStream() throws IOException {
170: if (dataHandler != null)
171: return dataHandler.getInputStream();
172: else
173: return new ByteArrayInputStream(data, 0, dataLen);
174: }
175:
176: /**
177: * Returns false if this object only has {@link DataHandler} and therefore
178: * {@link #get()} operation is likely going to be expensive.
179: */
180: public boolean hasData() {
181: return data != null;
182: }
183:
184: /**
185: * Gets the raw data. The size of the byte array maybe larger than the actual length.
186: */
187: public byte[] get() {
188: if (data == null) {
189: try {
190: ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(
191: 1024);
192: InputStream is = dataHandler.getDataSource()
193: .getInputStream();
194: baos.readFrom(is);
195: is.close();
196: data = baos.getBuffer();
197: dataLen = baos.size();
198: } catch (IOException e) {
199: // TODO: report the error to the unmarshaller
200: dataLen = 0; // recover by assuming length-0 data
201: }
202: }
203: return data;
204: }
205:
206: public int getDataLen() {
207: return dataLen;
208: }
209:
210: public String getMimeType() {
211: if (mimeType == null)
212: return "application/octet-stream";
213: return mimeType;
214: }
215:
216: /**
217: * Gets the number of characters needed to represent
218: * this binary data in the base64 encoding.
219: */
220: public int length() {
221: // for each 3 bytes you use 4 chars
222: // if the remainder is 1 or 2 there will be 4 more
223: get(); // fill in the buffer if necessary
224: return ((dataLen + 2) / 3) * 4;
225: }
226:
227: /**
228: * Encode this binary data in the base64 encoding
229: * and returns the character at the specified position.
230: */
231: public char charAt(int index) {
232: // we assume that the length() method is called before this method
233: // (otherwise how would the caller know that the index is valid?)
234: // so we assume that the byte[] is already populated
235:
236: int offset = index % 4;
237: int base = (index / 4) * 3;
238:
239: byte b1, b2;
240:
241: switch (offset) {
242: case 0:
243: return DatatypeConverterImpl.encode(data[base] >> 2);
244: case 1:
245: if (base + 1 < dataLen)
246: b1 = data[base + 1];
247: else
248: b1 = 0;
249: return DatatypeConverterImpl
250: .encode(((data[base] & 0x3) << 4)
251: | ((b1 >> 4) & 0xF));
252: case 2:
253: if (base + 1 < dataLen) {
254: b1 = data[base + 1];
255: if (base + 2 < dataLen)
256: b2 = data[base + 2];
257: else
258: b2 = 0;
259:
260: return DatatypeConverterImpl.encode(((b1 & 0xF) << 2)
261: | ((b2 >> 6) & 0x3));
262: } else
263: return '=';
264: case 3:
265: if (base + 2 < dataLen)
266: return DatatypeConverterImpl
267: .encode(data[base + 2] & 0x3F);
268: else
269: return '=';
270: }
271:
272: throw new IllegalStateException();
273: }
274:
275: /**
276: * Internally this is only used to split a text to a list,
277: * which doesn't happen that much for base64.
278: * So this method should be smaller than faster.
279: */
280: public CharSequence subSequence(int start, int end) {
281: StringBuilder buf = new StringBuilder();
282: get(); // fill in the buffer if we haven't done so
283: for (int i = start; i < end; i++)
284: buf.append(charAt(i));
285: return buf;
286: }
287:
288: /**
289: * Returns the base64 encoded string of this data.
290: */
291: public String toString() {
292: get(); // fill in the buffer
293: return DatatypeConverterImpl._printBase64Binary(data, 0,
294: dataLen);
295: }
296:
297: public void writeTo(char[] buf, int start) {
298: get();
299: DatatypeConverterImpl._printBase64Binary(data, 0, dataLen, buf,
300: start);
301: }
302:
303: public void writeTo(UTF8XmlOutput output) throws IOException {
304: // TODO: this is inefficient if the data source is note byte[] but DataHandler
305: get();
306: output.text(data, dataLen);
307: }
308: }
|