Storing Files
Building on our previous work with the StorageService interface, we'll now create a concrete implementation that stores files on disk for our restaurant review platform.
Creating the Service Implementation
The implementation of file storage requires a dedicated service class to handle the file operations effectively.
First, let's create a new package com.devtiro.restaurant.services.impl to house our service implementations.
Within this package, we'll create the FileSystemStorageService class that implements our StorageService interface:
@Service
@Slf4j
public class FileSystemStorageService implements StorageService {
@Value("${app.storage.location:uploads}")
private String storageLocation;
private Path rootLocation;
}The @Value annotation lets us configure the storage location through application properties, with a default value of "uploads" if not specified.
Initializing Storage Location
Before we can store files, we need to ensure our storage directory exists.
We'll use the @PostConstruct annotation to initialize our storage location after the bean is created:
@PostConstruct
public void init() {
rootLocation = Paths.get(storageLocation);
try {
Files.createDirectories(rootLocation);
} catch (IOException e) {
throw new StorageException("Could not initialize storage location", e);
}
}We use @PostConstruct instead of the constructor because we need to ensure all properties are injected before initialization.
Implementing File Storage
The store method handles the actual file storage operation:
@Override
public String store(MultipartFile file, String filename) {
try {
// Check for empty files
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file");
}
// Create final filename with extension
String extension = StringUtils.getFilenameExtension(file.getOriginalFilename());
String finalFilename = filename + "." + extension;
// Resolve and normalize the destination path
Path destinationFile = this.rootLocation.resolve(Paths.get(finalFilename))
.normalize().toAbsolutePath();
// Security check to prevent directory traversal
if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) {
throw new StorageException("Cannot store file outside current directory");
}
// Copy the file to the destination
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING);
}
return finalFilename;
} catch (IOException e) {
throw new StorageException("Failed to store file", e);
}
}This implementation includes several safety checks and uses our previously created StorageException for error handling.
Security and Management Considerations
While our implementation focuses on basic functionality, there are several important considerations for production environments:
- Files should be scanned for malware before storage
- File sizes should be limited to prevent disk space issues
- File types should be validated to prevent security vulnerabilities
- Proper file permissions should be set to protect stored data
Summary
- Implemented
FileSystemStorageServiceto store files on disk - Used
@Valueannotation to configure storage location path - Initialized storage directory with
@PostConstruct - Implemented secure file storage with path validation
- Added error handling using
StorageException