001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.resource; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022 023import org.apache.commons.io.input.BoundedInputStream; 024import org.apache.wicket.protocol.http.servlet.ResponseIOException; 025import org.apache.wicket.request.resource.AbstractResource.WriteCallback; 026import org.apache.wicket.request.resource.IResource.Attributes; 027import org.apache.wicket.util.io.IOUtils; 028import org.apache.wicket.util.io.Streams; 029import org.apache.wicket.util.lang.Args; 030 031/** 032 * Used to read a part of an input stream and writes it to the output stream of the response taken 033 * from attributes in {@link #writeData(org.apache.wicket.request.resource.IResource.Attributes)} 034 * method. 035 * 036 * @author Tobias Soloschenko 037 * @since 7.0.0 038 */ 039public class PartWriterCallback extends WriteCallback 040{ 041 /** 042 * The input stream to read from 043 */ 044 private final InputStream inputStream; 045 046 /** 047 * The total length to read if {@link #endbyte} is not specified 048 */ 049 private final Long contentLength; 050 051 /** 052 * The byte to start reading from. If omitted then the input stream will be read from its 053 * beginning 054 */ 055 private Long startbyte; 056 057 /** 058 * The end byte to read from the {@link #inputStream}. If omitted then the input stream will be 059 * read till its end 060 */ 061 private Long endbyte; 062 063 /** 064 * The size of the buffer that is used for the copying of the data 065 */ 066 private int bufferSize; 067 068 /** 069 * If the given input stream is going to be closed 070 */ 071 private boolean close = false; 072 073 074 /** 075 * Creates a part writer callback.<br> 076 * <br> 077 * Reads a part of the given input stream. If the startbyte parameter is not null the number of 078 * bytes are skipped till the stream is read. If the endbyte is not null the stream is read till 079 * endbyte, else to the end of the whole stream. If startbyte and endbyte is null the whole 080 * stream is copied. 081 * 082 * @param inputStream 083 * the input stream to read from 084 * @param contentLength 085 * content length of the input stream. Ignored if <em>endByte</em> is specified 086 * @param startbyte 087 * the start position to read from (if not null the number of bytes are skipped till 088 * the stream is read) 089 * @param endbyte 090 * the end position to read to (if not null the stream is going to be read till 091 * endbyte, else to the end of the whole stream) 092 */ 093 public PartWriterCallback(InputStream inputStream, Long contentLength, Long startbyte, 094 Long endbyte) 095 { 096 this.inputStream = inputStream; 097 this.contentLength = Args.notNull(contentLength, "contentLength"); 098 this.startbyte = startbyte; 099 this.endbyte = endbyte; 100 } 101 102 /** 103 * Writes the data 104 * 105 * @param attributes 106 * the attributes to get the output stream of the response 107 * @throws IOException 108 * if something went wrong while writing the data to the output stream 109 */ 110 @Override 111 public void writeData(Attributes attributes) throws IOException 112 { 113 try 114 { 115 OutputStream outputStream = attributes.getResponse().getOutputStream(); 116 byte[] buffer = new byte[getBufferSize()]; 117 118 if (startbyte != null || endbyte != null) 119 { 120 // skipping the first bytes which are 121 // requested to be skipped by the client 122 if (startbyte != null) 123 { 124 inputStream.skip(startbyte); 125 } 126 else 127 { 128 // If no start byte has been given set it to 0 129 // which means no bytes has been skipped 130 startbyte = 0L; 131 } 132 133 // If there are no end bytes given read the whole stream till the end 134 if (endbyte == null || Long.valueOf(-1).equals(endbyte)) 135 { 136 endbyte = contentLength; 137 } 138 139 BoundedInputStream boundedInputStream = null; 140 try 141 { 142 // Stream is going to be read from the starting point next to the skipped bytes 143 // till the end byte computed by the range between startbyte / endbyte 144 boundedInputStream = new BoundedInputStream(inputStream, 145 (endbyte - startbyte) + 1); 146 147 // The original input stream is going to be closed by the end of the request 148 // so set propagate close to false 149 boundedInputStream.setPropagateClose(false); 150 151 // The read bytes in the current buffer 152 int readBytes; 153 154 while ((readBytes = boundedInputStream.read(buffer)) != -1) 155 { 156 outputStream.write(buffer, 0, readBytes); 157 } 158 } 159 finally 160 { 161 IOUtils.closeQuietly(boundedInputStream); 162 } 163 } 164 else 165 { 166 // No range has been given so copy the content 167 // from input stream to the output stream 168 Streams.copy(inputStream, outputStream, getBufferSize()); 169 } 170 } 171 catch (ResponseIOException e) 172 { 173 // the client has closed the connection and 174 // doesn't read the stream further on 175 // (in tomcats 176 // org.apache.catalina.connector.ClientAbortException) 177 // we ignore this case 178 } 179 if (close) 180 { 181 IOUtils.close(inputStream); 182 } 183 } 184 185 /** 186 * Sets the buffer size used to send the data to the client 187 * 188 * @return the buffer size used to send the data to the client (default is 4096) 189 */ 190 public int getBufferSize() 191 { 192 return bufferSize > 0 ? bufferSize : 4096; 193 } 194 195 /** 196 * Sets the buffer size used to send the data to the client 197 * 198 * @param bufferSize 199 * the buffer size used to send the data to the client 200 * @return the part writer callback 201 */ 202 public PartWriterCallback setBufferSize(int bufferSize) 203 { 204 this.bufferSize = bufferSize; 205 return this; 206 } 207 208 /** 209 * If the given input stream is going to be closed 210 * 211 * @return if the given input stream is going to be closed 212 */ 213 public boolean isClose() 214 { 215 return close; 216 } 217 218 /** 219 * If set true the given input stream is going to be closed 220 * 221 * @param close 222 * if the given input stream is going to be closed 223 * @return the part writer callback 224 */ 225 public PartWriterCallback setClose(boolean close) 226 { 227 this.close = close; 228 return this; 229 230 } 231 232}