001package systems.dmx.files.provider; 002 003import systems.dmx.files.DiskQuotaCheck; 004import systems.dmx.files.FilesPlugin; 005import systems.dmx.files.UploadedFile; 006 007import systems.dmx.core.Topic; 008import systems.dmx.core.osgi.CoreActivator; 009import systems.dmx.core.service.CoreService; 010import systems.dmx.config.ConfigService; 011 012import org.apache.commons.fileupload.FileItem; 013import org.apache.commons.fileupload.FileItemFactory; 014import org.apache.commons.fileupload.disk.DiskFileItemFactory; 015import org.apache.commons.fileupload.servlet.ServletFileUpload; 016 017import java.io.InputStream; 018import java.io.IOException; 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Type; 021import java.util.List; 022import java.util.logging.Logger; 023 024import javax.servlet.http.HttpServletRequest; 025 026import javax.ws.rs.WebApplicationException; 027import javax.ws.rs.core.Context; 028import javax.ws.rs.core.MediaType; 029import javax.ws.rs.core.MultivaluedMap; 030import javax.ws.rs.ext.MessageBodyReader; 031import javax.ws.rs.ext.Provider; 032 033 034 035@Provider 036public class UploadedFileProvider implements MessageBodyReader<UploadedFile>, DiskQuotaCheck { 037 038 // ---------------------------------------------------------------------------------------------- Instance Variables 039 040 @Context 041 private HttpServletRequest request; 042 043 private Logger logger = Logger.getLogger(getClass().getName()); 044 045 // -------------------------------------------------------------------------------------------------- Public Methods 046 047 // *** MessageBodyReader Implementation *** 048 049 @Override 050 public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 051 // Note: unlike equals() isCompatible() ignores parameters like "charset" in "application/json;charset=UTF-8" 052 return type == UploadedFile.class && mediaType.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE); 053 } 054 055 @Override 056 public UploadedFile readFrom(Class<UploadedFile> type, Type genericType, Annotation[] annotations, 057 MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) 058 throws IOException, WebApplicationException { 059 try { 060 return parseMultiPart(); 061 } catch (Exception e) { 062 throw new RuntimeException("Deserializing an UploadedFile object failed", e); 063 } 064 } 065 066 // *** DiskQuotaCheck Implementation *** 067 068 @Override 069 public void check(long fileSize) { 070 CoreService dmx = CoreActivator.getCoreService(); 071 String username = dmx.getAccessControl().getUsername(request); 072 if (username == null) { 073 throw new RuntimeException("User <anonymous> has no disk quota"); 074 } 075 dmx.fireEvent(FilesPlugin.CHECK_DISK_QUOTA, username, fileSize, diskQuota(username)); 076 } 077 078 // ------------------------------------------------------------------------------------------------- Private Methods 079 080 private UploadedFile parseMultiPart() { 081 try { 082 UploadedFile file = null; 083 FileItemFactory factory = new DiskFileItemFactory(); // create a factory for disk-based file items 084 ServletFileUpload upload = new ServletFileUpload(factory); // create a new file upload handler 085 List<FileItem> items = upload.parseRequest(request); // parse the request 086 // FIXME: check if we can use a FileUpload low-level method to parse the request body instead of the 087 // entire request. Advantage: a) no need to inject the HttpServletRequest, b) no double work as the 088 // request is already parsed by jersey, c) no dependency on servlet-api. 089 logger.info("### Parsing multipart/form-data request (" + items.size() + " parts)"); 090 for (FileItem item : items) { 091 String fieldName = item.getFieldName(); 092 if (item.isFormField()) { 093 logger.info("### field \"" + fieldName + "\" => \"" + item.getString() + "\""); 094 } else { 095 if (file != null) { 096 throw new RuntimeException("Only single file uploads are supported"); 097 } 098 file = new UploadedFile(item, this); 099 logger.info("### field \"" + fieldName + "\" => " + file); 100 } 101 } 102 if (file == null) { 103 throw new RuntimeException("Request does not contain a file part"); 104 } 105 return file; 106 } catch (Exception e) { 107 throw new RuntimeException("Parsing multipart/form-data request failed", e); 108 } 109 } 110 111 private long diskQuota(String username) { 112 Topic usernameTopic = CoreActivator.getCoreService().getAccessControl().getUsernameTopic(username); 113 ConfigService cs = CoreActivator.getService(ConfigService.class); 114 Topic configTopic = cs.getConfigTopic("dmx.files.disk_quota", usernameTopic.getId()); 115 return 1024 * 1024 * configTopic.getSimpleValue().intValue(); 116 } 117}