/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.wicket.request.resource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.io.input.BoundedInputStream; import org.apache.wicket.protocol.http.servlet.ResponseIOException; import org.apache.wicket.request.resource.AbstractResource.WriteCallback; import org.apache.wicket.request.resource.IResource.Attributes; import org.apache.wicket.util.io.IOUtils; import org.apache.wicket.util.io.Streams; import org.apache.wicket.util.lang.Args; /** * Used to read a part of an input stream and writes it to the output stream of the response taken * from attributes in {@link #writeData(org.apache.wicket.request.resource.IResource.Attributes)} * method. * * @author Tobias Soloschenko * @since 7.0.0 */ public class PartWriterCallback extends WriteCallback { /** * The input stream to read from */ private final InputStream inputStream; /** * The total length to read if {@link #endbyte} is not specified */ private final Long contentLength; /** * The byte to start reading from. If omitted then the input stream will be read from its * beginning */ private Long startbyte; /** * The end byte to read from the {@link #inputStream}. If omitted then the input stream will be * read till its end */ private Long endbyte; /** * The size of the buffer that is used for the copying of the data */ private int bufferSize; /** * If the given input stream is going to be closed */ private boolean close = false; /** * Creates a part writer callback.<br> * <br> * Reads a part of the given input stream. If the startbyte parameter is not null the number of * bytes are skipped till the stream is read. If the endbyte is not null the stream is read till * endbyte, else to the end of the whole stream. If startbyte and endbyte is null the whole * stream is copied. * * @param inputStream * the input stream to read from * @param contentLength * content length of the input stream. Ignored if <em>endByte</em> is specified * @param startbyte * the start position to read from (if not null the number of bytes are skipped till * the stream is read) * @param endbyte * the end position to read to (if not null the stream is going to be read till * endbyte, else to the end of the whole stream) */ public PartWriterCallback(InputStream inputStream, Long contentLength, Long startbyte, Long endbyte) { this.inputStream = inputStream; this.contentLength = Args.notNull(contentLength, "contentLength"); this.startbyte = startbyte; this.endbyte = endbyte; } /** * Writes the data * * @param attributes * the attributes to get the output stream of the response * @throws IOException * if something went wrong while writing the data to the output stream */ @Override public void writeData(Attributes attributes) throws IOException { try { OutputStream outputStream = attributes.getResponse().getOutputStream(); byte[] buffer = new byte[getBufferSize()]; if (startbyte != null || endbyte != null) { // skipping the first bytes which are // requested to be skipped by the client if (startbyte != null) { inputStream.skip(startbyte); } else { // If no start byte has been given set it to 0 // which means no bytes has been skipped startbyte = 0L; } // If there are no end bytes given read the whole stream till the end if (endbyte == null || Long.valueOf(-1).equals(endbyte)) { endbyte = contentLength; } BoundedInputStream boundedInputStream = null; try { // Stream is going to be read from the starting point next to the skipped bytes // till the end byte computed by the range between startbyte / endbyte boundedInputStream = new BoundedInputStream(inputStream, (endbyte - startbyte) + 1); // The original input stream is going to be closed by the end of the request // so set propagate close to false boundedInputStream.setPropagateClose(false); // The read bytes in the current buffer int readBytes; while ((readBytes = boundedInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, readBytes); } } finally { IOUtils.closeQuietly(boundedInputStream); } } else { // No range has been given so copy the content // from input stream to the output stream Streams.copy(inputStream, outputStream, getBufferSize()); } } catch (ResponseIOException e) { // the client has closed the connection and // doesn't read the stream further on // (in tomcats // org.apache.catalina.connector.ClientAbortException) // we ignore this case } if (close) { IOUtils.close(inputStream); } } /** * Sets the buffer size used to send the data to the client * * @return the buffer size used to send the data to the client (default is 4096) */ public int getBufferSize() { return bufferSize > 0 ? bufferSize : 4096; } /** * Sets the buffer size used to send the data to the client * * @param bufferSize * the buffer size used to send the data to the client * @return the part writer callback */ public PartWriterCallback setBufferSize(int bufferSize) { this.bufferSize = bufferSize; return this; } /** * If the given input stream is going to be closed * * @return if the given input stream is going to be closed */ public boolean isClose() { return close; } /** * If set true the given input stream is going to be closed * * @param close * if the given input stream is going to be closed * @return the part writer callback */ public PartWriterCallback setClose(boolean close) { this.close = close; return this; } }