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}