| /* |
| * Copyright 2001-2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @author Rick Rineholt |
| */ |
| |
| package org.apache.axis.attachments; |
| |
| |
| import org.apache.axis.components.logger.LogFactory; |
| import org.apache.axis.utils.Messages; |
| import org.apache.commons.logging.Log; |
| |
| import javax.activation.DataHandler; |
| import javax.activation.DataSource; |
| import java.io.BufferedInputStream; |
| import java.io.IOException; |
| import java.util.StringTokenizer; |
| |
| |
| /** |
| * This class is a single part for DIME mulitpart message. |
| <pre> |
| DIME 1.0 format |
| 0 1 2 3 |
| 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | VERSION |B|E|C| TYPE_T| OPT_T | OPTIONS_LENGTH | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | ID_LENGTH | TYPE_LENGTH | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | DATA_LENGTH | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | / |
| / OPTIONS + PADDING / |
| / | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | / |
| / ID + PADDING / |
| / | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | / |
| / TYPE + PADDING / |
| / | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | / |
| / DATA + PADDING / |
| / | |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| </pre> |
| */ |
| |
| /** |
| * Holds one attachment DIME part. |
| */ |
| public class DimeBodyPart { |
| protected static Log log = |
| LogFactory.getLog(DimeBodyPart.class.getName()); |
| |
| protected Object data = null; |
| protected DimeTypeNameFormat dtnf = null; |
| protected byte[] type = null; |
| protected byte[] id = null; |
| static final byte POSITION_FIRST = (byte) 0x04; |
| static final byte POSITION_LAST = (byte) 0x02; |
| private static final byte CHUNK = 0x01; //Means set the chunk bit |
| private static final byte CHUNK_NEXT = 0x2; //Means this was part of a CHUNK |
| private static final byte ONLY_CHUNK = -1;//Means only one chunk was sent |
| private static final byte LAST_CHUNK = (byte)0;//Means many chunks were sent |
| private static int MAX_TYPE_LENGTH = (1 << 16) - 1; |
| private static int MAX_ID_LENGTH = (1 << 16) - 1; |
| |
| static final long MAX_DWORD = 0xffffffffL; |
| |
| // fixme: don't use? is this for inheritance only? I can't find any |
| // classes that extend this |
| protected DimeBodyPart() {} //do not use. |
| |
| /** |
| * Create a DIME Attachment Part. |
| * @param data a byte array containing the data as the attachment. |
| * @param format the type format for the data. |
| * @param type the type of the data |
| * @param id the ID for the DIME part. |
| * |
| */ |
| public DimeBodyPart(byte[] data, DimeTypeNameFormat format, |
| String type, String id) { |
| System.arraycopy(data, 0, this.data = new byte[ data.length], 0, data.length); |
| this.dtnf = format; |
| this.type = type.getBytes(); |
| if (this.type.length > MAX_TYPE_LENGTH) |
| throw new IllegalArgumentException(Messages.getMessage |
| ("attach.dimetypeexceedsmax", |
| "" + this.type.length, "" + MAX_TYPE_LENGTH)); |
| this.id = id.getBytes(); |
| if (this.id.length > MAX_ID_LENGTH) |
| throw new IllegalArgumentException( |
| Messages.getMessage("attach.dimelengthexceedsmax", "" + this.id.length, |
| "" + MAX_ID_LENGTH)); |
| } |
| |
| /** |
| * Create a DIME Attachment Part. |
| * @param dh the data for the attachment as a JAF datahadler. |
| * @param format the type format for the data. |
| * @param type the type of the data |
| * @param id the ID for the DIME part. |
| * |
| */ |
| public DimeBodyPart(DataHandler dh, |
| DimeTypeNameFormat format, String type, String id) { |
| this.data = dh; |
| this.dtnf = format; |
| if (type == null || type.length() == 0) |
| type = "application/octet-stream"; |
| this.type = type.getBytes(); |
| if (this.type.length > MAX_TYPE_LENGTH) |
| throw new IllegalArgumentException(Messages.getMessage( |
| "attach.dimetypeexceedsmax", |
| "" + this.type.length, "" + MAX_TYPE_LENGTH)); |
| this.id = id.getBytes(); |
| if (this.id.length > MAX_ID_LENGTH) |
| throw new IllegalArgumentException(Messages.getMessage( |
| "attach.dimelengthexceedsmax", |
| "" + this.id.length, "" + MAX_ID_LENGTH)); |
| } |
| |
| /** |
| * Create a DIME Attachment Part. |
| * @param dh the data for the attachment as a JAF datahadler. |
| * The type and foramt is derived from the DataHandler. |
| * @param id the ID for the DIME part. |
| * |
| */ |
| public DimeBodyPart(DataHandler dh, String id) { |
| this(dh, DimeTypeNameFormat.MIME, dh.getContentType(), id); |
| |
| String ct = dh.getContentType(); |
| |
| if (ct != null) { |
| ct = ct.trim(); |
| if (ct.toLowerCase().startsWith("application/uri")) { |
| StringTokenizer st = new StringTokenizer(ct, " \t;"); |
| String t = st.nextToken(" \t;"); |
| |
| if (t.equalsIgnoreCase("application/uri")) { |
| for (; st.hasMoreTokens();) { |
| t = st.nextToken(" \t;"); |
| if (t.equalsIgnoreCase("uri")) { |
| t = st.nextToken("="); |
| if (t != null) { |
| t = t.trim(); |
| if (t.startsWith("\"")) t = |
| t.substring(1); |
| |
| if (t.endsWith("\"")) t = |
| t.substring(0, t.length() - 1); |
| this.type = t.getBytes(); |
| this.dtnf = DimeTypeNameFormat.URI; |
| } |
| return; |
| } else if (t.equalsIgnoreCase("uri=")) { |
| t = st.nextToken(" \t;"); |
| if (null != t && t.length() != 0) { |
| t = t.trim(); |
| if (t.startsWith("\"")) t= |
| t.substring(1); |
| if (t.endsWith("\"")) t= |
| t.substring(0, t.length() - 1); |
| this.type = t.getBytes(); |
| this.dtnf = DimeTypeNameFormat.URI; |
| return; |
| } |
| } else if (t.toLowerCase().startsWith("uri=")) { |
| if (-1 != t.indexOf('=')) { |
| t = t.substring(t.indexOf('=')).trim(); |
| if (t.length() != 0) { |
| t = t.trim(); |
| if (t.startsWith("\"")) t = |
| t.substring(1); |
| |
| if (t.endsWith("\"")) |
| t = t.substring(0, t.length() - 1); |
| this.type = t.getBytes(); |
| this.dtnf = DimeTypeNameFormat.URI; |
| return; |
| |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Write to stream the data using maxchunk for the largest junk. |
| * |
| * @param os the <code>OutputStream</code> to write to |
| * @param position the position to write |
| * @param maxchunk the maximum length of any one chunk |
| * @throws IOException if there was a problem writing data to the stream |
| */ |
| void write(java.io.OutputStream os, byte position, long maxchunk) |
| throws java.io.IOException { |
| if (maxchunk < 1) throw new IllegalArgumentException( |
| Messages.getMessage("attach.dimeMaxChunkSize0", "" + maxchunk)); |
| if (maxchunk > MAX_DWORD) throw new IllegalArgumentException( |
| Messages.getMessage("attach.dimeMaxChunkSize1", "" + maxchunk)); |
| if (data instanceof byte[]) { |
| send(os, position, (byte[]) data, maxchunk); |
| } else if (data instanceof DynamicContentDataHandler) { |
| send(os, position, (DynamicContentDataHandler) data, maxchunk); |
| } else if (data instanceof DataHandler) { |
| DataSource source = ((DataHandler)data).getDataSource(); |
| DynamicContentDataHandler dh2 = new DynamicContentDataHandler(source); |
| send(os, position, dh2, maxchunk); |
| } |
| } |
| |
| /** |
| * Write to stream the data using the default largest chunk size. |
| * |
| * @param os the <code>OutputStream</code> to write to |
| * @param position the position to write |
| * @throws IOException if there was a problem writing data to the stream |
| */ |
| void write(java.io.OutputStream os, byte position) |
| throws java.io.IOException { |
| write(os, position, MAX_DWORD); |
| } |
| |
| private static final byte[] pad = new byte[4]; |
| |
| void send(java.io.OutputStream os, byte position, byte[] data, |
| final long maxchunk)throws java.io.IOException { |
| send(os, position, data, 0, data.length, maxchunk); |
| } |
| |
| void send(java.io.OutputStream os, byte position, byte[] data, |
| int offset, final int length, final long maxchunk) |
| throws java.io.IOException { |
| |
| byte chunknext = 0; |
| |
| do { |
| int sendlength = (int) Math.min(maxchunk, length - offset); |
| |
| sendChunk(os, position, data, offset, sendlength, (byte) |
| ((sendlength < (length - offset) ? CHUNK : 0) |
| | chunknext)); |
| offset += sendlength; |
| chunknext = CHUNK_NEXT; |
| } |
| while (offset < length); |
| } |
| |
| void send(java.io.OutputStream os, byte position, DataHandler dh, |
| final long maxchunk) throws java.io.IOException { |
| java.io.InputStream in = null; |
| try { |
| long dataSize = getDataSize(); |
| in = dh.getInputStream(); |
| byte[] readbuf = new byte[64 * 1024]; |
| int bytesread; |
| |
| sendHeader(os, position, dataSize, (byte) 0); |
| long totalsent = 0; |
| |
| do { |
| bytesread = in.read(readbuf); |
| if (bytesread > 0) { |
| os.write(readbuf, 0, bytesread); |
| totalsent += bytesread; |
| } |
| } |
| while (bytesread > -1); |
| os.write(pad, 0, dimePadding(totalsent)); |
| } |
| finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } |
| catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| /** |
| * Special case for dynamically generated content. |
| * maxchunk is currently ignored since the default is 2GB. |
| * The chunk size is retrieved from the DynamicContentDataHandler |
| * |
| * @param os |
| * @param position |
| * @param dh |
| * @param maxchunk |
| * @throws java.io.IOException |
| */ |
| void send(java.io.OutputStream os, byte position, DynamicContentDataHandler dh, |
| final long maxchunk) |
| throws java.io.IOException { |
| |
| BufferedInputStream in = new BufferedInputStream(dh.getInputStream()); |
| |
| try { |
| final int myChunkSize = dh.getChunkSize(); |
| |
| byte[] buffer1 = new byte[myChunkSize]; |
| byte[] buffer2 = new byte[myChunkSize]; |
| |
| int bytesRead1 = 0 , bytesRead2 = 0; |
| |
| bytesRead1 = in.read(buffer1); |
| |
| if(bytesRead1 < 0) { |
| sendHeader(os, position, 0, ONLY_CHUNK); |
| os.write(pad, 0, dimePadding(0)); |
| return; |
| } |
| byte chunkbyte = CHUNK; |
| do { |
| bytesRead2 = in.read(buffer2); |
| |
| if(bytesRead2 < 0) { |
| //last record...do not set the chunk bit. |
| //buffer1 contains the last chunked record!! |
| |
| //Need to distinguish if this is the first |
| //chunk to ensure the TYPE and ID are sent |
| if ( chunkbyte == CHUNK ){ |
| chunkbyte = ONLY_CHUNK; |
| } else { |
| chunkbyte = LAST_CHUNK; |
| } |
| sendChunk(os, position, buffer1, 0, bytesRead1, chunkbyte); |
| break; |
| } |
| |
| sendChunk(os, position, buffer1, 0, bytesRead1, chunkbyte); |
| //set chunk byte to next chunk flag to avoid |
| //sending TYPE and ID on subsequent chunks |
| chunkbyte = CHUNK_NEXT; |
| //now that we have written out buffer1, copy buffer2 into to buffer1 |
| System.arraycopy(buffer2,0,buffer1,0,myChunkSize); |
| bytesRead1 = bytesRead2; |
| |
| }while(bytesRead2 > 0); |
| } finally { |
| try { |
| in.close(); |
| } |
| catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| protected void sendChunk(java.io.OutputStream os, |
| final byte position, |
| byte[] data, byte chunk) throws java.io.IOException { |
| |
| sendChunk(os, position, data, 0, data.length, chunk); |
| } |
| |
| protected void sendChunk(java.io.OutputStream os, |
| final byte position, byte[] data, int offset, int length, |
| byte chunk) throws java.io.IOException { |
| |
| sendHeader(os, position, length, chunk); |
| os.write(data, offset, length); |
| os.write(pad, 0, dimePadding(length)); |
| } |
| |
| static final byte CURRENT_OPT_T = (byte) 0; |
| |
| protected void sendHeader(java.io.OutputStream os, |
| final byte position, |
| long length, byte chunk) throws java.io.IOException { |
| byte[] fixedHeader = new byte[12]; |
| |
| //If first chunk then send TYPE and ID |
| boolean isFirstChunk = ((chunk == CHUNK) || (chunk == ONLY_CHUNK)); |
| //If chunk is ONLY_NEXT then |
| //reset to CHUNK so CF is set. |
| //If chunk is ONLY_CHUNK (first and last chunk) |
| //then do not set CF since this is the only chunk |
| if ( chunk == CHUNK_NEXT ){ |
| chunk = CHUNK; |
| } else if ( chunk == ONLY_CHUNK ){ |
| chunk = LAST_CHUNK; |
| } |
| |
| //VERSION |
| fixedHeader[0] = (byte)((DimeMultiPart.CURRENT_VERSION << 3) & 0xf8); |
| |
| // B, E |
| fixedHeader[0] |= (byte) ((position & (byte) 0x6) |
| & ((chunk & CHUNK) != 0 ? ~POSITION_LAST : ~0) & |
| ((chunk & CHUNK_NEXT) != 0 ? ~POSITION_FIRST : ~0)); |
| fixedHeader[0] |= (chunk & CHUNK); |
| |
| boolean MB = 0 != (0x4 & fixedHeader[0]); |
| //TYPE_T |
| if ( MB || isFirstChunk ){ //If this is a follow on chunk dont send id again. |
| fixedHeader[1] = (byte) ((dtnf.toByte() << 4) & 0xf0); |
| } else { |
| fixedHeader[1] = (byte) 0x00; |
| } |
| |
| //OPT_T |
| fixedHeader[1] |= (byte) (CURRENT_OPT_T & 0xf); |
| |
| //OPTION_LENGTH |
| fixedHeader[2] = (byte) 0; |
| fixedHeader[3] = (byte) 0; |
| |
| //ID_LENGTH |
| if ( (MB || isFirstChunk) && (id != null && id.length > 0)) { //If this is a follow on chunk dont send id again. |
| fixedHeader[4] = (byte) ((id.length >>> 8) & 0xff); |
| fixedHeader[5] = (byte) ((id.length) & 0xff); |
| } else { |
| fixedHeader[4] = (byte) 0; |
| fixedHeader[5] = (byte) 0; |
| } |
| |
| //TYPE_LENGTH |
| if ( MB || isFirstChunk ) { |
| fixedHeader[6] = (byte) ((type.length >>> 8) & 0xff); |
| fixedHeader[7] = (byte) ((type.length) & 0xff); |
| } else { |
| fixedHeader[6] = (byte) 0; |
| fixedHeader[7] = (byte) 0; |
| } |
| |
| //DATA_LENGTH |
| fixedHeader[8] = (byte) ((length >>> 24) & 0xff); |
| fixedHeader[9] = (byte) ((length >>> 16) & 0xff); |
| fixedHeader[10] = (byte) ((length >>> 8) & 0xff); |
| fixedHeader[11] = (byte) (length & 0xff); |
| |
| os.write(fixedHeader); |
| |
| //OPTIONS + PADDING |
| // (NONE) |
| |
| //ID + PADDING |
| if ( (MB || isFirstChunk) && (id != null && id.length > 0)) { |
| os.write(id); |
| os.write(pad, 0, dimePadding(id.length)); |
| } |
| |
| //TYPE + PADDING |
| if ( MB || isFirstChunk ) { |
| os.write(type); |
| os.write(pad, 0, dimePadding(type.length)); |
| } |
| } |
| |
| static final int dimePadding(long l) { |
| return (int) ((4L - (l & 0x3L)) & 0x03L); |
| } |
| |
| long getTransmissionSize(long chunkSize) { |
| long size = 0; |
| size += id.length; |
| size += dimePadding(id.length); |
| size += type.length; |
| size += dimePadding(type.length); |
| //no options. |
| long dataSize = getDataSize(); |
| |
| if(0 == dataSize){ |
| size+=12; //header size. |
| }else{ |
| |
| long fullChunks = dataSize / chunkSize; |
| long lastChunkSize = dataSize % chunkSize; |
| |
| if (0 != lastChunkSize) size += 12; //12 bytes for fixed header |
| size += 12 * fullChunks; //add additional header size for each chunk. |
| size += fullChunks * dimePadding(chunkSize); |
| size += dimePadding(lastChunkSize); |
| size += dataSize; |
| } |
| return size; |
| } |
| |
| long getTransmissionSize() { |
| return getTransmissionSize(MAX_DWORD); |
| } |
| |
| protected long getDataSize() { |
| if (data instanceof byte[]) return ((byte[]) (data)).length; |
| if (data instanceof DataHandler) |
| return getDataSize((DataHandler) data); |
| return -1; |
| } |
| |
| protected long getDataSize(DataHandler dh) { |
| long dataSize = -1L; |
| |
| try { |
| DataSource ds = dh.getDataSource(); |
| |
| //Do files our selfs since this is costly to read in. Ask the file system. |
| // This is 90% of the use of attachments. |
| if (ds instanceof javax.activation.FileDataSource) { |
| javax.activation.FileDataSource fdh = |
| (javax.activation.FileDataSource) ds; |
| java.io.File df = fdh.getFile(); |
| |
| if (!df.exists()) { |
| throw new RuntimeException( |
| Messages.getMessage("noFile", |
| df.getAbsolutePath())); |
| } |
| dataSize = df.length(); |
| } else { |
| dataSize = 0; |
| java.io.InputStream in = ds.getInputStream(); |
| byte[] readbuf = new byte[64 * 1024]; |
| int bytesread; |
| |
| do { |
| bytesread = in.read(readbuf); |
| if (bytesread > 0) dataSize += bytesread; |
| } |
| while (bytesread > -1); |
| |
| if (in.markSupported()) { |
| //Leave the stream open for future reading |
| // and reset the stream pointer to the first byte |
| in.reset(); |
| } else { |
| //FIXME: bug http://nagoya.apache.org/jira/secure/ViewIssue.jspa?key=AXIS-1126 |
| //if we close this then how can we read the file? eh? |
| in.close(); |
| } |
| } |
| } catch (Exception e) { |
| //TODO: why are exceptions swallowed here? |
| log.error(Messages.getMessage("exception00"), e); |
| } |
| return dataSize; |
| } |
| } |