/*
* Copyright (C) 2012 Interactive Media Management
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package dk.i2m.converge.web.servlets;
import dk.i2m.converge.core.ConfigurationKey;
import dk.i2m.converge.core.DataNotFoundException;
import dk.i2m.converge.core.content.catalogue.*;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.core.utils.HttpUtils;
import dk.i2m.converge.ejb.facades.CatalogueFacadeLocal;
import dk.i2m.converge.ejb.facades.SystemFacadeLocal;
import dk.i2m.converge.ejb.facades.UserFacadeLocal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
/**
* Servlet accepting file uploads for {@link Catalogue}s.
*
* @author Allan Lykke Christensen
*/
public class FileUploadServlet extends HttpServlet {
private static final Logger LOG = Logger.getLogger(FileUploadServlet.class.
getName());
@EJB private CatalogueFacadeLocal catalogueFacade;
@EJB private SystemFacadeLocal systemFacade;
@EJB private UserFacadeLocal userFacade;
/**
* Operation determine how the {@link FileUploadServlet} should react to the
* incoming {@link HttpServletRequest}.
*/
private enum Operation {
/** Operation for creating a new {@link MediaItem}. */
NEW_MEDIA_ITEM,
/** Operation for creating a new {@link MediaItemRendition}. */
NEW_MEDIA_ITEM_RENDITION,
/** Operation for updating an existing {@link MediaItemRendition}. */
UPDATE_MEDIA_ITEM_RENDITION,
/** Unknown/unsupported operation requested. */
UNKNOWN
}
/** Authenticated user requesting an operation from the servlet. */
private UserAccount user = null;
/** Value returned after the SUCCESS message. */
private String returnValue = "";
/**
* Processes the upload request by reading each chunk at a time and
* combining it to a single file.
* <p/>
* @param request Servlet request
* @param response Servlet response
* @throws ServletException If a servlet-specific error occurs
* @throws IOException If an I/O error occurs
*/
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
boolean access = securityCheck(request);
if (!access) {
return;
}
//Initialization for chunk management.
boolean bLastChunk = false;
int numChunk = 0;
response.setContentType("text/plain");
try {
Enumeration paraNames = request.getParameterNames();
String pkey;
String pvalue;
if (paraNames != null) {
while (paraNames.hasMoreElements()) {
pkey = (String) paraNames.nextElement();
pvalue = request.getParameter(pkey);
if (pkey.equals("jufinal")) {
bLastChunk = pvalue.equals("1");
} else if (pkey.equals("jupart")) {
numChunk = Integer.parseInt(pvalue);
}
}
}
Operation operation = determineOperation(request);
if (operation == Operation.UNKNOWN) {
LOG.warning("Unknown operation requested");
return;
}
String mediaItemId = null;
String renditionId = null;
String mediaItemRenditionId = null;
int ourMaxMemorySize = 10000000;
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Set factory constraints
factory.setSizeThreshold(ourMaxMemorySize);
factory.setRepository(new File(getTemporaryDirectory()));
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
if (request.getContentType() != null && request.getContentType().
startsWith("multipart/form-data")) {
List<FileItem> items = upload.parseRequest(request);
// Process the uploaded items
File fout;
for (FileItem fileItem : items) {
if (!fileItem.isFormField()) {
//If we are in chunk mode, we add ".partN" at the end of the file, where N is the chunk number.
String uploadedFilename = fileItem.getName() + (numChunk
> 0 ? ".part" + numChunk : "");
fout = new File(getTemporaryDirectory() + (new File(
uploadedFilename)).getName());
LOG.log(Level.FINE, "File out: {0}", fout.toString());
fileItem.write(fout);
// Execute requested operation
switch (operation) {
case NEW_MEDIA_ITEM:
executeNewMediaItem(request, fileItem);
break;
case UPDATE_MEDIA_ITEM_RENDITION:
executeUpdateRendition(request, fileItem);
break;
case NEW_MEDIA_ITEM_RENDITION:
executeNewRendition(request, fileItem);
break;
default:
LOG.log(Level.WARNING, "Unknown operation");
break;
}
// Chunk management: if it was the last chunk, let's
// recover the complete file by concatenating all chunk
// parts.
if (bLastChunk) {
LOG.log(Level.FINEST,
"Last chunk received: Rebuilding complete file ({0})",
fileItem.getName());
//First: construct the final filename.
FileInputStream fis;
FileOutputStream fos =
new FileOutputStream(getTemporaryDirectory()
+ fileItem.getName());
int nbBytes;
byte[] byteBuff = new byte[1024];
String filename;
for (int i = 1; i <= numChunk; i += 1) {
filename = fileItem.getName() + ".part" + i;
LOG.log(Level.FINEST, "Concatenating {0}",
filename);
fis = new FileInputStream(getTemporaryDirectory()
+ filename);
while ((nbBytes = fis.read(byteBuff)) >= 0) {
LOG.log(Level.FINEST, "Nb bytes read: {0}",
nbBytes);
fos.write(byteBuff, 0, nbBytes);
}
fis.close();
}
fos.close();
}
// End of chunk management
fileItem.delete();
}
}
}
response.getWriter().println("SUCCESS");
response.getWriter().print(this.returnValue);
} catch (Exception e) {
LOG.log(Level.SEVERE, "File upload failed. " + e.getMessage(), e);
}
}
/**
* Checks if the user is authenticated and exist in the database.
* <p/>
* @param request {@link HttpServletRequest} received from the user
* @return {@code true} if the user is authenticated and existing, otherwise
* {@code false}
*/
private boolean securityCheck(HttpServletRequest request) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal == null) {
this.user = null;
LOG.log(Level.WARNING, "Unauthorised access to FileUploadServlet "
+ "attempted from {0}", request.getRemoteAddr());
return false;
}
String uid = userPrincipal.getName();
try {
this.user = userFacade.findById(uid);
} catch (DataNotFoundException ex) {
this.user = null;
LOG.log(Level.SEVERE, ex.getMessage());
return false;
}
return true;
}
private Operation determineOperation(HttpServletRequest request) {
if (request.getParameter("uploadType") != null && request.getParameter(
"uploadType").equalsIgnoreCase(
"replaceRendition")) {
return Operation.UPDATE_MEDIA_ITEM_RENDITION;
} else if (request.getParameter("uploadType") != null && request.
getParameter("uploadType").equalsIgnoreCase("newRendition")) {
return Operation.NEW_MEDIA_ITEM_RENDITION;
} else if (request.getParameter("uploadType") != null && request.
getParameter("uploadType").equalsIgnoreCase("newMediaItem")) {
return Operation.NEW_MEDIA_ITEM;
} else {
return Operation.UNKNOWN;
}
}
private void executeNewMediaItem(HttpServletRequest request,
FileItem fileItem) {
LOG.log(Level.FINE, "Creating new media item");
String catalogueId = request.getParameter("catalogueId");
if (catalogueId == null) {
LOG.log(Level.WARNING, "Missing catalogueId parameter");
return;
}
Catalogue catalogue = null;
try {
catalogue =
catalogueFacade.findCatalogueById(Long.valueOf(catalogueId));
} catch (DataNotFoundException ex) {
LOG.log(Level.WARNING, "Invalid catalogue: {0}",
new Object[]{catalogueId});
return;
} catch (NumberFormatException ex) {
LOG.log(Level.WARNING, "Invalid catalogue: {0}",
new Object[]{catalogueId});
return;
}
MediaItem mediaItem = new MediaItem();
mediaItem.setTitle(FilenameUtils.getBaseName(fileItem.getName()));
mediaItem.setStatus(MediaItemStatus.SELF_UPLOAD);
mediaItem.setCatalogue(catalogue);
mediaItem.setOwner(user);
mediaItem.setByLine("");
mediaItem = catalogueFacade.create(mediaItem);
this.returnValue = "" + mediaItem.getId();
MediaItemRendition mir = new MediaItemRendition();
mir.setMediaItem(mediaItem);
mir.setRendition(catalogue.getOriginalRendition());
mediaItem.getRenditions().add(mir);
String filename = HttpUtils.getFilename(fileItem.getName());
File uploadedFile = new File(getTemporaryDirectory(), filename);
try {
mir = catalogueFacade.create(uploadedFile,
mediaItem, mir.getRendition(), filename,
fileItem.getContentType(), true);
LOG.log(Level.FINE,
"New media item and rendition created: {0} / {1}",
new Object[]{mediaItem.getId(), mir.getId()});
} catch (IOException ioex) {
LOG.log(Level.WARNING, "Could not create file. "
+ ioex.getMessage(), ioex);
}
uploadedFile.delete();
}
private void executeUpdateRendition(HttpServletRequest request,
FileItem fileItem) {
// Retrieve parameters from request
String paramMediaItemId = request.getParameter("mediaItemId");
String paramRenditionId = request.getParameter("renditionId");
Long mediaItemId;
Long renditionId;
try {
mediaItemId = Long.valueOf(paramMediaItemId);
renditionId = Long.valueOf(paramRenditionId);
} catch (NumberFormatException ex) {
LOG.log(Level.WARNING, "Invalid values supplied. {0}",
ex.getMessage());
return;
}
// Retrieve the MediaItemRendition
MediaItem mediaItem;
Rendition rendition;
MediaItemRendition mediaItemRendition;
try {
mediaItem = catalogueFacade.findMediaItemById(mediaItemId);
rendition = catalogueFacade.findRenditionById(renditionId);
mediaItemRendition = mediaItem.findRendition(rendition);
} catch (DataNotFoundException ex) {
LOG.log(Level.WARNING, "Unknown data requested. {0}",
ex.getMessage());
return;
} catch (RenditionNotFoundException ex) {
LOG.log(Level.WARNING, "Unknown rendition requested. {0}", ex.
getMessage());
return;
}
// Update MediaItemRendition with the uploaded file
File uploadedFile =
new File(getTemporaryDirectory(), fileItem.getName());
try {
mediaItemRendition = catalogueFacade.update(uploadedFile, fileItem.
getName(), fileItem.getContentType(), mediaItemRendition, true);
this.returnValue = "" + mediaItemRendition.getId();
LOG.log(Level.FINE, "Media item #{0} was updated",
new Object[]{mediaItemRendition.getId()});
} catch (IOException ex) {
LOG.log(Level.SEVERE, "Could not update catalogue. {0}", ex.
getMessage());
}
uploadedFile.delete();
}
private void executeNewRendition(HttpServletRequest request,
FileItem fileItem) {
// Retrieve parameters from request
String paramMediaItemId = request.getParameter("mediaItemId");
String paramRenditionId = request.getParameter("renditionId");
Long mediaItemId;
Long renditionId;
try {
mediaItemId = Long.valueOf(paramMediaItemId);
renditionId = Long.valueOf(paramRenditionId);
} catch (NumberFormatException ex) {
LOG.log(Level.WARNING, "Invalid values supplied. {0}",
ex.getMessage());
return;
}
// Retrieve objects from database
MediaItem mediaItem;
Rendition rendition;
try {
mediaItem = catalogueFacade.findMediaItemById(mediaItemId);
rendition = catalogueFacade.findRenditionById(renditionId);
} catch (DataNotFoundException ex) {
LOG.log(Level.WARNING, "Unknown data requested. {0}", ex.getMessage());
return;
}
File uploadedFile = new File(getTemporaryDirectory(), fileItem.getName());
MediaItemRendition mediaItemRendition;
try {
mediaItemRendition = catalogueFacade.create(uploadedFile, mediaItem, rendition, fileItem.getName(), fileItem.getContentType(), true);
LOG.log(Level.FINE, "New media item rendition created: {0}", mediaItemRendition.getId());
this.returnValue = "" + mediaItemRendition.getId();
} catch (IOException ex) {
LOG.log(Level.SEVERE, "Could not create media item rendition. {0}", ex.getMessage());
}
uploadedFile.delete();
}
/**
* Gets the temporary directory for storing incoming files.
* <p/>
* @return Temporary directory for storing incoming files
*/
private String getTemporaryDirectory() {
String home = systemFacade.getProperty(
ConfigurationKey.WORKING_DIRECTORY);
String tempDirectory = home + "/tmp/";
return tempDirectory;
}
// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP
* <code>GET</code> method.
* <p/>
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Handles the HTTP
* <code>POST</code> method.
* <p/>
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Returns a short description of the servlet.
* <p/>
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}