javaspring-bootbackendhttp-gethttp-delete

Server and database file handling (post, get, delete) is working only partially in my spring boot project


Only POST is working. GET and DELETE are throwing the following error when testing through Postman:

{
    "timestamp": "2023-04-06T14:01:59.898+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/secure/test/file/bull.png"
}

I haven't worked with Multipart files before and I have a feeling that it's something regarding that that goes wrong, but I can't quite put my finger on what exactly. I'll put the relevant pieces of code in the following code blocks.

package server.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import server.services.StorageService;

import java.io.IOException;

@SpringBootApplication
@RestController
@RequestMapping(value = {"/secure/{username}/{password}/file", "/secure/{username}/file"})
public class FileController {
    @Autowired
    private StorageService service;
    @PostMapping("/add")
    public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        String uploadFile = service.uploadFile(file);
        return ResponseEntity.status(HttpStatus.OK)
                .body(uploadFile);
    }
    @GetMapping("/{fileName}")
    public ResponseEntity<?> outputFile(@PathVariable String fileName){
        byte[] fileData=service.outputFile(fileName);
        return ResponseEntity.status(HttpStatus.OK)
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(fileData);
    }
    @DeleteMapping("/{fileName}")
    public ResponseEntity<?> deleteFile(@PathVariable String fileName) {
        String message = service.deleteFile(fileName);
        return ResponseEntity.status(HttpStatus.OK)
                .body(message);
    }
}
package server.services;

import commons.FileData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import server.database.StorageRepository;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Optional;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

@Service
public class StorageService {
    @Autowired
    private StorageRepository repository;

    public String uploadFile(MultipartFile file) throws IOException {
        repository.save(new FileData(file.getOriginalFilename(),
                file.getContentType(), compressFile(file.getBytes())));
        return "File uploaded succesfully: "+ file.getOriginalFilename();
    }
    public byte[] outputFile(String fileName){
        Optional<FileData> dbFileData = repository.findByName(fileName);
        byte[] file=decompressFile(dbFileData.get().getFileData());
        return file;
    }
    public String deleteFile(String fileName) {
        Optional<FileData> dbFileData = repository.findByName(fileName);
        if (dbFileData.isPresent()) {
            repository.delete(dbFileData.get());
            return "File deleted successfully: " + fileName;
        } else {
            return "File not found: " + fileName;
        }
    }
    public String getType(String fileName){
        Optional<FileData> dbFileData = repository.findByName(fileName);
        if (dbFileData.isPresent()) {
            return dbFileData.get().getType();
        } else {
            return "File not found: " + fileName;
        }
    }
    public static byte[] compressFile(byte[] data) {
        Deflater deflater = new Deflater();
        deflater.setLevel(Deflater.BEST_COMPRESSION);
        deflater.setInput(data);
        deflater.finish();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
        byte[] tmp = new byte[4*1024];
        while (!deflater.finished()) {
            int size = deflater.deflate(tmp);
            outputStream.write(tmp, 0, size);
        }
        try {
            outputStream.close();
        } catch (Exception ignored) {
        }
        return outputStream.toByteArray();
    }
    public static byte[] decompressFile(byte[] data) {
        Inflater inflater = new Inflater();
        inflater.setInput(data);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
        byte[] tmp = new byte[4*1024];
        try {
            while (!inflater.finished()) {
                int count = inflater.inflate(tmp);
                outputStream.write(tmp, 0, count);
            }
            outputStream.close();
        } catch (Exception ignored) {
        }
        return outputStream.toByteArray();
    }
}
package commons;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name="FileData")
@Data
public class FileData {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String type;
    @Lob
    @Column(name="filedata")
    private byte[] fileData;
    public FileData(String name, String type, byte[] fileData) {
        this.name = name;
        this.type = type;
        this.fileData = fileData;
    }
    public byte[] getFileData() {
        return fileData;
    }

    public String getType() {
        return type;
    }
    public String getName() {
        return name;
    }

    public Long getId() {
        return id;
    }
}

Solution

  • The problem was the missing default constructor of the FileData class.

    Even though I had one with parameters, that's not considered default as the default constructor has to be without parameters. Adding this line to the FileData class solved the issue

    public FileData() {}