Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.picdrop.service.implementation; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Provider; import com.picdrop.exception.ApplicationException; import com.picdrop.exception.ErrorMessageCode; import com.picdrop.guice.factory.ResourceContainerFactory; import com.picdrop.guice.names.File; import com.picdrop.guice.provider.FileRepositoryProvider; import com.picdrop.guice.provider.ResourceContainer; import com.picdrop.guice.provider.UploadHandlerProvider; import static com.picdrop.helper.LogHelper.*; import com.picdrop.io.Processor; import com.picdrop.io.repository.FileRepository; import com.picdrop.model.FileType; import com.picdrop.model.RequestContext; import com.picdrop.model.Share; import com.picdrop.model.ShareReference; import com.picdrop.model.resource.Collection; import com.picdrop.model.resource.FileResource; import com.picdrop.model.resource.ResourceDescriptor; import com.picdrop.model.user.RegisteredUser; import com.picdrop.model.user.User; import com.picdrop.repository.AwareRepository; import com.picdrop.repository.Repository; import com.picdrop.security.authentication.Permission; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tika.Tika; import org.apache.tika.metadata.Metadata; /** * * @author i330120 */ @Path("/app/resources") @Consumes("application/json") @Produces("application/json") public class FileResourceService { Logger log = LogManager.getLogger(this.getClass()); Repository<String, FileResource> repo; AwareRepository<String, Share, User> srepo; Repository<String, Collection.CollectionItem> cirepo; Repository<String, Collection> crepo; FileRepository<String> fileRepo; List<Processor<FileResource>> processors; final List<String> mimeImage = Arrays.asList("image/jpeg", "image/png", "image/tiff"); ServletFileUpload upload; @Inject Provider<RequestContext> contextProv; ResourceContainerFactory instProvFac; Tika tika; @Inject public FileResourceService(Repository<String, FileResource> repo, AwareRepository<String, Share, User> srepo, Repository<String, Collection.CollectionItem> cirepo, Repository<String, Collection> crepo, @File FileRepositoryProvider fileRepoProv, @File List<Processor<FileResource>> processors) { this.repo = repo; this.srepo = srepo; this.cirepo = cirepo; this.crepo = crepo; try { this.fileRepo = fileRepoProv.get(); } catch (IOException ex) { log.fatal("Unable to initialize file repository", ex); } this.processors = processors; log.trace(SERVICE, "created with ({},{},{},{},{},{})", repo, srepo, cirepo, crepo, fileRepo, processors); } public void setUploadHandler(ServletFileUpload upload) { this.upload = upload; } @Inject public void setUploadHandler(UploadHandlerProvider uploadProvider) { try { setUploadHandler(uploadProvider.get()); } catch (IOException ex) { this.log.fatal("Unable to initialize upload handler", ex); } } @Inject public void setResourceContainerFactory(ResourceContainerFactory instProvFac) { this.instProvFac = instProvFac; } public Tika getTika() { return tika; } @Inject public void setTika(Tika tika) { this.tika = tika; } protected List<FileItem> parseRequest(HttpServletRequest request) throws FileUploadException { List<FileItem> files = null; files = upload.parseRequest(request); return files; } protected FileType parseMimeType(FileItem file) throws ApplicationException, IOException { FileType mime = FileType.forName(file.getContentType()); if (mime.isUnknown()) { throw new ApplicationException().status(400) .devMessage(String.format("Invalid mime type detected '%s'", file.getContentType())) .code(ErrorMessageCode.BAD_UPLOAD_MIME); } Metadata mdata = new Metadata(); InputStream in = file.getInputStream(); String tikaMime = null; try { tikaMime = tika.detect(in, mdata); } finally { in.close(); } log.debug(SERVICE, "Mime types resolved. HTTP Header: '{}' Tika: '{}'", file.getContentType(), tikaMime); if (!FileType.forName(tikaMime).isCovering(mime)) { throw new ApplicationException().status(400).devMessage(String .format("Mime type mismatch, expected: '%s' detected: '%s'", file.getContentType(), tikaMime)) .code(ErrorMessageCode.BAD_UPLOAD_MIME); } return mime; } protected FileResource processCreateUpdate(FileResource e, FileItem file) throws ApplicationException { log.entry(e); FileResource loce = e; String fileId; // Pre store log.debug(SERVICE, "Pre-Store: Processing file"); ResourceContainer cnt = instProvFac.create(file); try { for (Processor<FileResource> p : processors) { loce = p.onPreStore(loce, cnt); } } catch (IOException ex) { throw new ApplicationException(ex).status(500).code(ErrorMessageCode.ERROR_UPLOAD) .devMessage("Error while pre-store phase: " + ex.getMessage()); } // Store try { fileId = fileRepo.write(loce.getFileId(), cnt); loce.setFileId(fileId); } catch (IOException ex) { throw new ApplicationException(ex).status(500).code(ErrorMessageCode.ERROR_UPLOAD) .devMessage("Error while store phase: " + ex.getMessage()); } if (Strings.isNullOrEmpty(loce.getId())) { loce = this.repo.save(loce); } else { loce = this.repo.update(loce.getId(), loce); } // Post store log.debug(SERVICE, "Post-Store: Processing file"); cnt = instProvFac.create(loce); try { for (Processor<FileResource> p : processors) { loce = p.onPostStore(loce, cnt); } } catch (IOException ex) { throw new ApplicationException(ex).status(500).code(ErrorMessageCode.ERROR_UPLOAD) .devMessage("Error while post-store phase: " + ex.getMessage()); } log.traceExit(loce); return loce; } protected void processDelete(FileResource e) throws ApplicationException { log.entry(e); boolean res = false; // Delete citems referring this res log.debug(SERVICE, "Pre-Delete: Retrieving collection items"); List<Collection.CollectionItem> cis; try { cis = this.cirepo.queryNamed("citems.with.resource", e.getId()); } catch (IOException ex) { throw new ApplicationException(ex).code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("Error while querying citems: " + ex.getMessage()); } log.debug(SERVICE, "Pre-Delete: Deleting collection items"); for (Collection.CollectionItem ci : cis) { this.cirepo.delete(ci.getId()); Collection c = ci.getParentCollection().resolve(false); c.removeItem(ci); this.crepo.update(c.getId(), c); } // Deleting Shares referring this res log.debug(SERVICE, "Pre-Delete: Deleting shares"); for (ShareReference sref : e.getShares()) { this.srepo.delete(sref.getId()); } e.setShares(new ArrayList<>()); // Pre process log.debug(SERVICE, "Pre-Delete: Processing file"); try { for (Processor<FileResource> p : processors) { p.onPreDelete(e); } } catch (IOException ex) { throw new ApplicationException(ex).code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("Error while pre-delete phase: " + ex.getMessage()); } // Delete in DB res = this.repo.delete(e.getId()); if (!res) { throw new ApplicationException().code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("Repository returned 'false'"); } // Delete file try { res = fileRepo.delete(e.getFileId()); } catch (IOException ex) { // Roleback this.repo.save(e); throw new ApplicationException(ex).code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("Error while delete file phase: " + ex.getMessage()); } if (!res) { // Roleback this.repo.save(e); throw new ApplicationException().code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("File repository returned 'false'"); } // Post process log.debug(SERVICE, "Post-Delete: Processing file"); try { for (Processor<FileResource> p : processors) { p.onPostDelete(e); } } catch (IOException ex) { throw new ApplicationException(ex).code(ErrorMessageCode.ERROR_DELETE).status(500) .devMessage("Error while post-delete phase: " + ex.getMessage()); } log.traceExit(); } @GET @Path("/{id}") @Permission("read") public FileResource getResource(@PathParam("id") String id) throws ApplicationException { log.entry(id); FileResource fr = this.repo.get(id); if (fr == null) { throw new ApplicationException().status(404).code(ErrorMessageCode.NOT_FOUND) .devMessage(String.format("Object with id '%s' not found", id)); } log.info(SERVICE, "FileResource found"); log.traceExit(fr); return fr; } @GET @Path("/") @Permission("read") public List<FileResource> listResource() { return this.repo.list(); } @POST @Path("/") @Permission("write") @Consumes("multipart/form-data") public List<FileResource> create(@Context HttpServletRequest request) throws ApplicationException { log.traceEntry(); List<FileResource> res = new ArrayList<>(); List<FileItem> files; log.debug(SERVICE, "Parsing multipart request"); try { files = parseRequest(request); } catch (FileUploadException ex) { throw new ApplicationException(ex).status(400).devMessage(ex.getMessage()) .code(ErrorMessageCode.BAD_UPLOAD); } log.debug(SERVICE, "Processing file items"); for (FileItem file : files) { if (!file.isFormField()) { validateFileItem(file); FileResource r = new FileResource(); r.setName(file.getName()); r.setSize(file.getSize()); r.setOwner(contextProv.get().getPrincipal().to(RegisteredUser.class)); try { log.debug(SERVICE, "Validating mime type"); FileType mime = parseMimeType(file); r.setDescriptor(ResourceDescriptor.get(mime)); res.add(processCreateUpdate(r, file)); } catch (IOException ex) { throw new ApplicationException(ex).status(500).devMessage(ex.getMessage()) .code(ErrorMessageCode.ERROR_INTERNAL); } } } log.info(SERVICE, "FileResource created"); log.traceExit(res); return res; } @PUT @Path("/{id}") @Permission("write") public FileResource update(@PathParam("id") String id, FileResource entity) throws ApplicationException { log.entry(id, entity); if (entity == null) { throw new ApplicationException().status(400).code(ErrorMessageCode.BAD_REQUEST_BODY); } FileResource r = getResource(id); if (r == null) { throw new ApplicationException().status(404).code(ErrorMessageCode.NOT_FOUND) .devMessage(String.format("Object with id '%s' not found", id)); } log.debug(SERVICE, "Performing object merge"); try { r = r.merge(entity); } catch (IOException ex) { throw new ApplicationException(ex).status(500).code(ErrorMessageCode.ERROR_OBJ_MERGE) .devMessage(ex.getMessage()); } log.info(SERVICE, "FileResource updated"); return log.traceExit(repo.update(id, r)); } @PUT @Path("/{id}") @Permission("write") @Consumes("multipart/form-data") public FileResource updateFile(@PathParam("id") String id, @Context HttpServletRequest request) throws ApplicationException { log.entry(id); FileResource r = getResource(id); List<FileItem> files = null; if (r == null) { throw new ApplicationException().status(404).code(ErrorMessageCode.NOT_FOUND) .devMessage(String.format("Object with id '%s' not found", id)); } log.debug(SERVICE, "Parsing multipart request"); try { files = parseRequest(request); } catch (FileUploadException ex) { throw new ApplicationException(ex).status(400).devMessage(ex.getMessage()) .code(ErrorMessageCode.BAD_UPLOAD); } log.debug(SERVICE, "Processing file items"); for (FileItem file : files) { if (!file.isFormField()) { try { validateFileItem(file); log.debug(SERVICE, "Validating mime type"); FileType mime = parseMimeType(file); r.setDescriptor(ResourceDescriptor.get(mime)); r = processCreateUpdate(r, file); } catch (IOException ex) { throw new ApplicationException(ex).status(500).devMessage(ex.getMessage()) .code(ErrorMessageCode.ERROR_INTERNAL); } } } log.info(SERVICE, "FileResource updated"); log.traceExit(r); return r; } @DELETE @Permission("write") @Path("/{id}") public void delete(@PathParam("id") String id) throws ApplicationException { log.entry(id); FileResource r = getResource(id); if (r != null) { processDelete(r); } else { throw new ApplicationException().status(404).code(ErrorMessageCode.NOT_FOUND) .devMessage(String.format("Object with id '%s' not found", id)); } log.info(SERVICE, "FileResource deleted"); log.traceExit(); } protected void validateFileItem(FileItem file) throws ApplicationException { if (file.getName().length() > 1024) { throw new ApplicationException().code(ErrorMessageCode.BAD_RESOURCE_NAME) .devMessage(String.format("Recorded file name length (%d/1024)'", file.getName().length())) .status(400); } } }