/*
* 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.
*/
/*
* FileUpload.java
*
* Created on March 7, 2006, 1:49 PM
*
*
* @author Mark Basler
* @author Binu John
*/
package org.apache.olio.webapp.fileupload;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.olio.webapp.controller.WebConstants;
import org.apache.olio.webapp.util.ServiceLocator;
import org.apache.olio.webapp.util.WebappConstants;
import org.apache.olio.webapp.util.WebappUtil;
import org.apache.olio.webapp.util.fs.FileSystem;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class uses Apache Commons FileUpload to parse the multi-part mime that is sent via the HttpServletRequest InputStream.
* This class also relies on Faces to populate the fileUploadStatus in this Backing Bean on each request.
*
* @author basler
* @author Binu John
*/
public class FileUploadHandler {
private static Logger logger = Logger.getLogger(FileUploadHandler.class.getName());
private FileUploadStatus fileUploadStatus = null;
private FileItemIterator itemIter;
private FileItemStream item;
private Hashtable<String, String> requestParams;
/**
* Default location of upload directory is /domain_dir/lib/upload, unless the Sun Appserver system property exits, then it will
* use the domain's lib/upload directory instead
*/
/** Creates a new instance of FileUpload */
public FileUploadHandler() {
}
/**
* Write status of current/last upload to the response stream. This method assumes that the fileUploadStatus will
* be set using the managed property functionality of a backing bean. This relationship is specified in the
* faces-config.xml file. This method is accessed through the Shale-remoting dynamic framework.
*/
public static void handleFileStatus(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Cache-Control", "no-cache");
response.setContentType("text/xml;charset=UTF-8");
// use managed bean session object to allow the monitoring of the fileupload based on componentName
//FileUploadStatus status=getFileUploadStatus();
HttpSession session = request.getSession();
FileUploadStatus status = (FileUploadStatus) session.getAttribute(FileUploadUtil.FILE_UPLOAD_STATUS);
try {
// write out response based on fileupload status.
PrintWriter writer = response.getWriter();
writer.println("<response>");
if (status != null) {
writer.println("<message>");
writer.println(status.getMessage());
writer.println("</message>");
writer.println("<status>");
writer.println(status.getStatus());
writer.println("</status>");
writer.println("<current_item>");
writer.println(status.getCurrentItem());
writer.println("</current_item>");
writer.println("<percent_complete>");
writer.println(String.valueOf(status.getPercentageComplete()));
writer.println("</percent_complete>");
} else {
// no status in session yet
writer.println("<message>");
writer.println("No Status");
writer.println("</message>");
writer.println("<status>");
writer.println("No Status");
writer.println("</status>");
writer.println("<current_item>");
writer.println("None");
writer.println("</current_item>");
writer.println("<percent_complete>");
writer.println("0");
writer.println("</percent_complete>");
}
writer.println("</response>");
writer.flush();
} catch (IOException iox) {
//logger.severe("FileUploadPhaseListener error writting AJAX response : " + iox);
logger.log(Level.SEVERE, "FileUploadHandler Error writing AJAX response: ", iox);
}
logger.log(Level.FINE, "STATUS RETURN = " + status);
}
/**
* Write status of current/last upload to the response stream. This method assumes that the fileUploadStatus will
* be set using the managed property functionality of a backing bean. This relationship is specified in the
* faces-config.xml file. This method is accessed through the Shale-remoting dynamic framework.
*/
public static void handleFileUploadFinal(HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.log(Level.FINE, "** In Final upload for STATUS RETURN = ");
response.setHeader("Cache-Control", "no-cache");
response.setContentType("text/xml;charset=UTF-8");
// use managed bean session object to allow the monitoring of the fileupload based on componentName
//FileUploadStatus status=getFileUploadStatus();
HttpSession session = request.getSession();
FileUploadStatus status = (FileUploadStatus) session.getAttribute(FileUploadUtil.FILE_UPLOAD_STATUS);
writeUploadResponse(response.getWriter(), status);
}
/**
* Handles the initial fields up to the first upload field. This will
* allow creating the database entry and obtaining the auto-generated
* ids.
* @return A hash table with the initial field values
*/
public Hashtable<String, String> getInitialParams(HttpServletRequest request, HttpServletResponse response) {
// print out header for
Enumeration enumx = request.getHeaderNames();
String key = "";
String listx = "";
while (enumx.hasMoreElements()) {
key = (String) enumx.nextElement();
listx += "\n" + key + ":" + request.getHeader(key);
}
logger.fine("Incoming Header Item:" + listx);
// enable progress bar (this managed bean that is in the session could be comp specific, but I can't create the component specific
// session object until I have the components name. For now use static key through backing bean).
// Use session to allow the monitoring of the fileupload based
HttpSession session = request.getSession();
FileUploadStatus status = new FileUploadStatus();
session.setAttribute(FileUploadUtil.FILE_UPLOAD_STATUS, status);
setFileUploadStatus(status);
// Create hashtable to hold uploaded data so it can be used by custom post extension
Hashtable<String, String> htUpload = new Hashtable<String, String>();
// set to set hashtable for final retrieval
status.setUploadItems(htUpload);
// get size of upload and set status
long totalSizeOfUpload = request.getContentLength();
status.setTotalUploadSize(totalSizeOfUpload);
// Check that we have a proper file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
// Streaming API typically provide better performance for file uploads.
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
try {
// Now we should have the componentsName and upload directory to setup remaining upload of file items
String compName = htUpload.get(FileUploadUtil.COMPONENT_NAME);
status.setName(compName);
// Parse the request and return list of "FileItem" whle updating status
FileItemIterator iter = upload.getItemIterator(request);
status.setReadingComplete();
while (iter.hasNext()) {
item = iter.next();
if (item.isFormField()) {
// handle a form item being uploaded
String itemName = item.getFieldName();
// process form(non-file) item200002
int size = formItemFound(item, htUpload);
updateSessionStatus(itemName, size);
logger.fine("Form field item:" + itemName);
} else {
// At the first find of an uploaded file, stop.
// We need to insert our record first in order
// to find the id.
break;
}
}
itemIter = iter;
} catch (Exception e) {
status.setUploadError("FileUpload didn't complete successfully. Exception received:" + e.toString());
logger.log(Level.SEVERE, "file.upload.exception", e);
}
}
fileUploadStatus = status;
requestParams = htUpload;
return htUpload;
}
/**
* Invoke the fileupload process that reads the input from the HttpServletRequest inputStream. Since this
* component accesses the HttpServletRequest directly, this component currently may not work from within a portlet.
* This method assumes that the fileUploadStatus will be set using the managed property functionality of a backing bean.
* This relationship is specified in the faces-config.xml file. This method is accessed through the Shale-remoting dynamic framework.
*/
public Hashtable<String, String> handleFileUpload(String id, HttpServletRequest request, HttpServletResponse response) {
File fileDir = null;
String compName = requestParams.get(FileUploadUtil.COMPONENT_NAME);
String serverLocationDir = requestParams.get(compName + "_" + FileUploadUtil.SERVER_LOCATION_DIR);
logger.finest("\n*** locationDir=" + serverLocationDir);
if (serverLocationDir == null) {
serverLocationDir = WebappConstants.WEBAPP_IMAGE_DIRECTORY;
fileDir = new File(serverLocationDir);
fileDir.mkdirs();
} else {
// make sure directory exists, don't create automatically for security reasons
fileDir = new File(serverLocationDir);
}
logger.finest("serverLocationDir = " + serverLocationDir);
// make sure dir exists and is writable
if (fileDir == null || !fileDir.isDirectory() || !fileDir.canWrite()) {
// error, directory doesn't exist or isn't writable
fileUploadStatus.setUploadError("Directory \"" + fileDir.toString() + "\" doesn't exist!");
logger.log(Level.SEVERE, "directory.inaccessable:", fileDir.toString());
return null;
}
try {
for (;;) {
if (item.isFormField()) {
// handle a form item being uploaded
String itemName = item.getFieldName();
// process form(non-file) item
int size = formItemFound(item, requestParams);
updateSessionStatus(itemName, size);
logger.fine("Form field item:" + itemName);
} else {
fileItemFound(item, requestParams, serverLocationDir, id);
}
if (itemIter.hasNext())
item = itemIter.next();
else
break;
}
// put upload to 100% to handle rounding errors in status calc
fileUploadStatus.setUploadComplete();
logger.fine("Final session status - " + fileUploadStatus);
} catch (Exception e) {
fileUploadStatus.setUploadError("FileUpload didn't complete successfully. Exception received:" + e.toString());
logger.log(Level.SEVERE, "file.upload.exception:", e);
}
return requestParams;
}
/**
* Handle upload of a standard form item
*
* @param item The Commons Fileupload item being loaded
* @param htUpload The Status Hashtable that contains the items that have been uploaded for post-processing use
*/
protected int formItemFound(FileItemStream item, Hashtable<String, String> htUpload) {
int size = 0;
InputStream is = null;
try {
String key = item.getFieldName();
StringBuilder strb = new StringBuilder();
byte[] buf = new byte[128];
is = item.openStream();
// Read from the stream
int i = 0;
while ((i = is.read(buf)) != -1) {
strb.append(new String(buf, 0, i));
size += i;
}
String value = strb.toString();
// put in Hashtable for later access
logger.fine("Inserting form item in map " + key + " = " + value);
htUpload.put(key, value);
} catch (IOException ex) {
Logger.getLogger(FileUploadHandler.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
is.close();
} catch (IOException ex) {
Logger.getLogger(FileUploadHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
return size;
}
/**
* Handle upload of a "file" form item
*
* @param item The Commons Fileupload item being loaded
* @param htUpload The Status Hashtable that contains the items that have been uploaded for post-processing use
* @param serverLocationDir The Status Hashtable that contains the items that have been uploaded for post-processing use
*/
protected void fileItemFound(FileItemStream item, Hashtable<String, String> htUpload,
String serverLocationDir, String id) throws Exception {
String fileLocation = null;
String fileName = item.getName();
if (fileName != null && !fileName.equals("")) {
// see if IE on windows which send full path with item, but just filename
// check for both separators because client OS my be different that server OS
// check for unix separator
logger.fine("Have item - " + fileName + " - in name= " + item.getFieldName());
int iPos = fileName.lastIndexOf("/");
if (iPos > -1) {
fileName = fileName.substring(iPos + 1);
logger.fine("Have full path, need to truncate \n" + item.getName() + "\n" + fileName);
}
// check for windows separator
iPos = fileName.lastIndexOf("\\");
if (iPos > -1) {
fileName = fileName.substring(iPos + 1);
logger.fine("Have full path, need to truncate \n" + item.getName() + "\n" + fileName);
}
fileLocation = serverLocationDir + File.separator + fileName;
String thumbnailName;
String ext = WebappUtil.getFileExtension(fileName);
if (item.getFieldName().equals(WebConstants.UPLOAD_PERSON_IMAGE_PARAM)) {
fileName = "P" + id;
thumbnailName = fileName + 'T' + ext;
// Append the extension
fileName += ext;
writeWithThumbnail(item, fileName, thumbnailName);
htUpload.put(WebConstants.UPLOAD_PERSON_IMAGE_PARAM, fileName);
htUpload.put(WebConstants.UPLOAD_PERSON_IMAGE_THUMBNAIL_PARAM, thumbnailName);
} else if (item.getFieldName().equals(WebConstants.UPLOAD_PERSON_IMAGE_THUMBNAIL_PARAM)) {
fileName = "P" + id + "T" + ext;
write(item, fileName);
htUpload.put(WebConstants.UPLOAD_PERSON_IMAGE_THUMBNAIL_PARAM, fileName);
} else if (item.getFieldName().equals(WebConstants.UPLOAD_EVENT_IMAGE_PARAM)) {
//String submitter = htUpload.get(WebConstants.SUBMITTER_USER_NAME_PARAM);
fileName = "E" + id;
thumbnailName = fileName + 'T' + ext;
fileName += ext;
writeWithThumbnail(item, fileName, thumbnailName);
htUpload.put(WebConstants.UPLOAD_EVENT_IMAGE_PARAM, fileName);
htUpload.put(WebConstants.UPLOAD_EVENT_IMAGE_THUMBNAIL_PARAM, thumbnailName);
} else if (item.getFieldName().equals("eventThumbnail")) {
fileName = "E" + id + 'T' + ext;
write(item, fileName);
htUpload.put(WebConstants.UPLOAD_EVENT_IMAGE_THUMBNAIL_PARAM, fileName);
} else if (item.getFieldName().equals("upload_event_literature")) {
fileName = "E" + id + 'L' + ext;
fileLocation = serverLocationDir + "/" + fileName;
write(item, fileLocation);
htUpload.put(WebConstants.UPLOAD_LITERATURE_PARAM, fileName);
} else {
logger.warning("******** what is this? item is " + item.getFieldName());
}
logger.fine("Writing item to " + fileLocation);
// store image location in Hashtable for later access
String fieldName = item.getFieldName();
logger.fine("Inserting form item in map " + fieldName + " = " + fileName);
htUpload.put(fieldName, fileName);
// insert location
String key = fieldName + FileUploadUtil.FILE_LOCATION_KEY;
logger.fine("Inserting form item in map " + key + " = " + fileLocation);
htUpload.put(key, fileLocation);
}
}
/**
* Write response of fileupload. This method can also call a DeferredMethod that is entered by using the
* "postProcessingMethod" attribute of the FileUploadTag. This post processing method must have a signature of
* "javax.faces.context.FacesContext, java.util.Hashtable, com.sun.javaee.blueprints.components.ui.fileupload.FileUploadStatus"
* If the post processing method wants to provide a custom response, the method can call the "enableCustomReturn" method on the
* FileUploadStatus object so that the default fileupload response will not be sent.
*
* @param writer The writer to write the output
* @param status The Status object that contains the items that have been uploaded for post-processing use
*/
public static void writeUploadResponse(PrintWriter writer, FileUploadStatus status) {
Hashtable<String, String> htUpload = status.getUploadItems();
try {
logger.log(Level.FINE, "\n***In writeUploadResponse");
// make sure session data exists
if (htUpload != null) {
String compName = htUpload.get(FileUploadUtil.COMPONENT_NAME);
//FileUploadStatus status=getFileUploadStatus();
// check to see if user has custom post-processing method
String postProcessingMethod = htUpload.get(compName + "_" + FileUploadUtil.POST_PROCESSING_METHOD);
logger.log(Level.FINE, "\n*** postProcessingMethod = " + postProcessingMethod);
if (postProcessingMethod != null) {
// call post-processing method
// extension mechanism
}
// see if custom response set
if (!status.isCustomReturnEnabled()) {
// return default response
logger.log(Level.FINE, "FINAL STATUS - " + status);
writer.println("<response>");
writer.println("<message>");
writer.println(status.getMessage());
writer.println("</message>");
writer.println("<status>");
writer.println(status.getStatus());
writer.println("</status>");
writer.println("<duration>");
writer.println(String.valueOf(status.getUploadTime()));
writer.println("</duration>");
writer.println("<duration_string>");
writer.println(status.getUploadTimeString());
writer.println("</duration_string>");
writer.println("<start_date>");
writer.println(status.getStartUploadDate().toString());
writer.println("</start_date>");
writer.println("<end_date>");
writer.println(status.getEndUploadDate().toString());
writer.println("</end_date>");
writer.println("<upload_size>");
writer.println(String.valueOf(status.getTotalUploadSize()));
writer.println("</upload_size>");
writer.println("</response>");
writer.flush();
}
} else {
// error no data in session
writer.println("<response>");
writer.println("<message>");
writer.println("Error, no session data available!");
writer.println("</message>");
writer.println("<status>");
writer.println(FileUploadStatus.UPLOAD_ERROR);
writer.println("</status>");
writer.println("</response>");
writer.flush();
}
} catch (Exception e) {
logger.log(Level.FINE, "FileUploadPhaseListener error writting AJAX response : " + e);
logger.log(Level.SEVERE, "response.exeception", e);
}
}
/**
* write out file item to a file using the Apache Commons FileUpload FileItem.write method as a guide,
* so the output could be monitored
*
* @param item The Commons Fileupload item being loaded
* @param fileName File name to write uploaded data.
* @throws Exception Exceptions propagated from Apache Commons Fileupload classes
*/
public void write(FileItemStream item, String fileName) throws Exception {
// use name for update of session
String itemName = item.getName();
ServiceLocator locator = ServiceLocator.getInstance();
FileSystem fs = locator.getFileSystem();
logger.fine("Getting fileItem from memory - " + itemName);
OutputStream fout = fs.create(fileName);
// It would have been more efficient to use NIO if we are writing to
// the local filesystem. However, since we need to support DFS,
// a simple solution is provided.
// TO DO: Optimize write if required.
try {
byte[] buf = new byte[8192];
int count, size = 0;
InputStream is = item.openStream();
while ((count = is.read(buf)) != -1) {
fout.write(buf, 0, count);
size += count;
}
updateSessionStatus("", size);
} finally {
if (fout != null) {
fout.close();
}
}
}
/**
*
* @param item FileItemStream from the file upload handler
* @param imageName name to save the image.
* @param thumbnailName name to save the thumbnail. Extension is appended.
* @throws java.lang.Exception
*/
public void writeWithThumbnail(FileItemStream item,
String imageName, String thumbnailName) throws Exception {
// use name for update of session
String itemName = item.getName();
ServiceLocator locator = ServiceLocator.getInstance();
FileSystem fs = locator.getFileSystem();
logger.fine("Getting fileItem from memory - " + itemName);
OutputStream imgOut = null;
OutputStream thumbOut = null;
InputStream is = null;
try {
imgOut = fs.create(imageName);
thumbOut = fs.create(thumbnailName);
// It would have been more efficient to use NIO if we are writing to
// the local filesystem. However, since we need to support DFS,
// a simple solution is provided.
// TO DO: Optimize write if required.
is = item.openStream();
FileUploadStatus status = getFileUploadStatus();
status.setCurrentItem(itemName);
WebappUtil.saveImageWithThumbnail(is, imgOut, thumbOut, status);
} finally {
if (imgOut != null)
try {
imgOut.close();
} catch (IOException e) {
}
if (thumbOut != null)
try {
thumbOut.close();
} catch (IOException e) {
}
if (is != null)
try {
is.close();
} catch (IOException e) {
}
}
}
public void updateSessionStatus(String itemName, long incrementAmount) {
FileUploadStatus status = getFileUploadStatus();
status.setCurrentItem(itemName);
status.incrementCurrentSizeWritten(incrementAmount);
}
public void setFileUploadStatus(FileUploadStatus status) {
fileUploadStatus = status;
}
public FileUploadStatus getFileUploadStatus() {
return fileUploadStatus;
}
}