/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.xmlui.cocoon;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Response;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.http.HttpEnvironment;
import org.apache.cocoon.environment.http.HttpResponse;
import org.apache.cocoon.reading.AbstractReader;
import org.apache.cocoon.util.ByteRange;
import org.dspace.app.itemexport.ItemExport;
import org.dspace.app.xmlui.utils.AuthenticationUtil;
import org.dspace.app.xmlui.utils.ContextUtil;
import org.dspace.core.Context;
import org.xml.sax.SAXException;
/**
* @author Jay Paz
*/
public class ItemExportDownloadReader extends AbstractReader implements Recyclable
{
/**
* Messages to be sent when the user is not authorized to view
* a particular bitstream. They will be redirected to the login
* where this message will be displayed.
*/
private static final String AUTH_REQUIRED_HEADER = "xmlui.ItemExportDownloadReader.auth_header";
private static final String AUTH_REQUIRED_MESSAGE = "xmlui.ItemExportDownloadReader.auth_message";
/**
* How big of a buffer should we use when reading from the bitstream before
* writting to the HTTP response?
*/
protected static final int BUFFER_SIZE = 8192;
/**
* When should a download expire in milliseconds. This should be set to
* some low value just to prevent someone hiting DSpace repeatily from
* killing the server. Note: 60000 milliseconds are in a second.
*
* Format: minutes * seconds * milliseconds
*/
protected static final int expires = 60 * 60 * 60000;
/** The Cocoon response */
protected Response response;
/** The Cocoon request */
protected Request request;
/** The bitstream file */
protected InputStream compressedExportInputStream;
/** The compressed export's reported size */
protected long compressedExportSize;
protected String compressedExportName;
/**
* Set up the export reader.
*
* See the class description for information on configuration options.
*/
public void setup(SourceResolver resolver, Map objectModel, String src,
Parameters par) throws ProcessingException, SAXException,
IOException
{
super.setup(resolver, objectModel, src, par);
try
{
this.request = ObjectModelHelper.getRequest(objectModel);
this.response = ObjectModelHelper.getResponse(objectModel);
Context context = ContextUtil.obtainContext(objectModel);
// Get our parameters that identify the bitstream
String fileName = par.getParameter("fileName", null);
// Is there a User logged in and does the user have access to read it?
if (!ItemExport.canDownload(context, fileName))
{
if(context.getCurrentUser()!=null){
// A user is logged in, but they are not authorized to read this bitstream,
// instead of asking them to login again we'll point them to a friendly error
// message that tells them the bitstream is restricted.
String redictURL = request.getContextPath() + "/restricted-resource?name=" + fileName;
HttpServletResponse httpResponse = (HttpServletResponse)
objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
httpResponse.sendRedirect(redictURL);
return;
}
else{
// The user does not have read access to this bitstream. Inturrupt this current request
// and then forward them to the login page so that they can be authenticated. Once that is
// successfull they will request will be resumed.
AuthenticationUtil.interruptRequest(objectModel, AUTH_REQUIRED_HEADER, AUTH_REQUIRED_MESSAGE, null);
// Redirect
String redictURL = request.getContextPath() + "/login";
HttpServletResponse httpResponse = (HttpServletResponse)
objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
httpResponse.sendRedirect(redictURL);
return;
}
}
// Success, bitstream found and the user has access to read it.
// Store these for later retreval:
this.compressedExportInputStream = ItemExport.getExportDownloadInputStream(fileName, context.getCurrentUser());
this.compressedExportSize = ItemExport.getExportFileSize(fileName);
this.compressedExportName = fileName;
}
catch (Exception e)
{
throw new ProcessingException("Unable to read bitstream.",e);
}
}
/**
* Write the actual data out to the response.
*
* Some implementation notes,
*
* 1) We set a short expires time just in the hopes of preventing someone
* from overloading the server by clicking reload a bunch of times. I
* realize that this is nowhere near 100% effective but it may help in some
* cases and shouldn't hurt anything.
*
*/
public void generate() throws IOException, SAXException,
ProcessingException
{
if (this.compressedExportInputStream == null)
{
return;
}
byte[] buffer = new byte[BUFFER_SIZE];
int length = -1;
response.setDateHeader("Expires", System.currentTimeMillis()
+ expires);
response.setHeader("Content-disposition","attachement; filename=" + this.compressedExportName );
ByteRange byteRange = null;
// Turn off partial downloads, they cause problems
// and are only rarely used. Specifically some windows pdf
// viewers are incapable of handling this request. You can
// uncomment the following lines to turn this feature back on.
// response.setHeader("Accept-Ranges", "bytes");
// String ranges = request.getHeader("Range");
// if (ranges != null)
// {
// try
// {
// ranges = ranges.substring(ranges.indexOf('=') + 1);
// byteRange = new ByteRange(ranges);
// }
// catch (NumberFormatException e)
// {
// byteRange = null;
// if (response instanceof HttpResponse)
// {
// // Respond with status 416 (Request range not
// // satisfiable)
// response.setStatus(416);
// }
// }
// }
if (byteRange != null)
{
String entityLength;
String entityRange;
if (this.compressedExportSize != -1)
{
entityLength = "" + this.compressedExportSize;
entityRange = byteRange.intersection(
new ByteRange(0, this.compressedExportSize)).toString();
}
else
{
entityLength = "*";
entityRange = byteRange.toString();
}
response.setHeader("Content-Range", entityRange + "/"
+ entityLength);
if (response instanceof HttpResponse)
{
// Response with status 206 (Partial content)
((HttpResponse) response).setStatus(206);
}
int pos = 0;
int posEnd;
while ((length = this.compressedExportInputStream.read(buffer)) > -1)
{
posEnd = pos + length - 1;
ByteRange intersection = byteRange
.intersection(new ByteRange(pos, posEnd));
if (intersection != null)
{
out.write(buffer, (int) intersection.getStart()
- pos, (int) intersection.length());
}
pos += length;
}
}
else
{
response.setHeader("Content-Length", String
.valueOf(this.compressedExportSize));
while ((length = this.compressedExportInputStream.read(buffer)) > -1)
{
out.write(buffer, 0, length);
}
out.flush();
}
}
/**
* Returns the mime-type of the bitstream.
*/
public String getMimeType()
{
return ItemExport.COMPRESSED_EXPORT_MIME_TYPE;
}
/**
* Recycle
*/
public void recycle() {
this.response = null;
this.request = null;
this.compressedExportInputStream = null;
this.compressedExportSize = 0;
}
}