FileService Interface
public interface FileService { GridFSDBFile find(String id); Optional<GridFsResource> findAsResource(String id); String store(String name, MultipartFile file); void delete(String id); }
FileService Implementation
We can't use GridFsTemplate.getResource(String location)
here to get the resource because there might be files with the same name. To find the expected file, we need to find a list of the resources that all match the same filename and then filter them by the id after retrieving all the files. This is acceptable because at that point, the InputStream
hasn't been requested yet. GridFsTemplate.getResources(String locationPattern)
takes an Ant matching pattern and it returns all resources whose filename passes the test.
GridFsTemplate.store()
returns an Object
whose string literal could be converted to a ObjectId
type. For instance,
ObjectId objectId = new ObjectId(GridFsTemplate.store(...).toString())
.
@Service public class FileServiceImpl implements FileService { private GridFsTemplate gridFsTemplate; @Autowired public FileServiceImpl(final GridFsTemplate gridFsTemplate) { this.gridFsTemplate = gridFsTemplate; } @Override public GridFSDBFile find(final String id) { ObjectId objectId = new ObjectId(id); return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(objectId))); } @Override public Optional<GridFsResource> findAsResource(final String id) { return Stream.of(gridFsTemplate.getResources(find(id).getFilename() + "*")) .filter(gridFsResource -> gridFsResource.getId().toString().equals(id)) .findAny(); } @Override public String store(String name, final MultipartFile file) { try { String filename = file.getOriginalFilename(); if (name != null) { filename = FileNameUtils.getExtension(filename) .map(ext -> name + "." + ext) .orElse(name); } InputStream inputStream = file.getInputStream(); GridFSFile gridFSFile = gridFsTemplate.store( inputStream, filename, file.getContentType()); if (gridFSFile != null) { return gridFSFile.getId().toString(); } throw new FileException("Failed to save file"); } catch (IOException e) { throw new FileException(e); } } @Override public void delete(final String id) { ObjectId objectId = new ObjectId(id); gridFsTemplate.delete(new Query(Criteria.where("_id").is(objectId))); } }
FileController
@RestController @RequestMapping("/api/v1/files") public class FileController { private FileService fileService; @Autowired public FileController(final FileService fileService) { this.fileService = fileService; } @PostMapping public Attachment uploadAttachment(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) { String id = fileService.store(name, file); return new Attachment(name, id); } @GetMapping("/{id}") public ResponseEntity<InputStreamResource> downloadFile(@PathVariable("id") String id) { Optional<GridFsResource> resourceOptional = fileService.findAsResource(id); return resourceOptional.map(resource -> { try { return ResponseEntity .ok() .contentType(MediaType.valueOf(resource.getContentType())) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + resource.getFilename() + """) .body(new InputStreamResource(resource.getInputStream())); } catch (IOException e) { throw new FileException(e); } }).orElseThrow(() -> new FileException("File not found")); } @DeleteMapping("/{id}") public void deleteFile(@PathVariable("id") String id) { fileService.delete(id); } class Attachment { private String name; private String fileId; Attachment(final String name, final String fileId) { this.name = name; this.fileId = fileId; } // Getters and setters... } }