Skip to main content

Overview

The FileStorageService manages the storage and retrieval of files in the local filesystem. It creates organized folder structures per person, calculates file metadata (SHA-256, size, path), and provides secure access to stored documents. Package: co.edu.unbosque.ccdigital.service Key Features:
  • Person-based folder organization
  • Automatic SHA-256 hash calculation
  • File metadata tracking (size, path, original name)
  • Path normalization across operating systems
  • Security validation for file access

Configuration

The service uses the FileStorageProperties class to read configuration:
ccdigital.fs.base-path=/path/to/storage
Environment Variable: CCDIGITAL_FS_BASE_PATH
The application requires read/write permissions on the configured base path.

Person Folder Structure

Folder Naming Convention

Personal folders are created using a normalized combination of last name and first name: Rules:
  • Concatenates lastName + firstName
  • Removes all spaces
  • Normalizes to ASCII (removes accents/diacritics)
  • Keeps only alphanumeric characters
Example: María José GonzálezGonzalezMariaJose

Storage Path Format

Files are stored with relative paths normalized to forward slashes (/):
{basePath}/ApellidoNombre/documento.pdf

Core Methods

Creates the person’s folder if it doesn’t exist.Parameters:
  • person - Person entity with name information
Returns: Path - Absolute path to the person’s folderThrows: IllegalStateException if folder creation fails
Path personFolder = fileStorageService.ensurePersonFolder(person);
// Example: /storage/GonzalezMariaJose
Stores a file on disk and calculates metadata.Behavior:
  • Generates filename if originalFilename is empty: upload-{timestamp}
  • Writes file with REPLACE_EXISTING policy
  • Calculates SHA-256 hash after storage
  • Returns structured metadata in StoredFileInfo
Parameters:
  • person - Person entity (folder owner)
  • file - Multipart file from HTTP request
Returns: StoredFileInfo - Contains:
  • relativePath - Path relative to basePath (using /)
  • size - File size in bytes
  • sha256 - SHA-256 hash (hex, lowercase)
  • originalName - Original filename
Throws: IllegalStateException on storage error
StoredFileInfo info = fileStorageService.storePersonFile(person, multipartFile);
System.out.println("SHA-256: " + info.getSha256());
System.out.println("Size: " + info.getSize() + " bytes");
Builds a relative path for a file without storing it.Parameters:
  • person - Person entity
  • filename - Target filename
Returns: String - Relative path with / separatorsUsage:
String relativePath = fileStorageService.buildRelativePath(person, "diploma.pdf");
// Returns: "GonzalezMariaJose/diploma.pdf"
Resolves the absolute path from a FileRecord’s stored relative path.Parameters:
  • fileRecord - Entity containing storagePath field
Returns: Path - Absolute, normalized path to the fileThrows: IllegalStateException if storagePath is emptyUsage:
Path absolutePath = fileStorageService.resolvePath(fileRecord);
File file = absolutePath.toFile();
This method normalizes Windows-style backslashes (\\) to forward slashes for cross-platform compatibility.
Loads a file as a Spring Resource for download/viewing.Parameters:
  • fileRecord - Entity with file metadata
Returns: Resource - FileSystemResource ready for HTTP responseUsage:
Resource resource = fileStorageService.loadAsResource(fileRecord);
return ResponseEntity.ok()
    .contentType(MediaType.APPLICATION_PDF)
    .body(resource);

FileRecord Entity

Files are tracked in the files table with the following structure:
FieldTypeDescription
idLongPrimary key
document_idLongFK to documents (document definition)
person_document_idLongFK to person_documents
original_nameString(300)Original filename with extension
mime_typeString(150)File MIME type
byte_sizeLongFile size in bytes
sha256_hexString(64)SHA-256 hash (hexadecimal)
stored_asEnumStorage strategy: PATH, BLOB, S3
file_pathString(800)Relative path (for PATH strategy)
contentBLOBBinary content (for BLOB strategy)
versionIntegerFile version (auto-incremented)
uploaded_by_userLongUser ID who uploaded
uploaded_atTimestampUpload timestamp (DB-managed)
Storage Strategies:
  • PATH (default): File stored on filesystem, path in file_path
  • BLOB: File stored in database content field
  • S3: File stored in S3 (path in file_path)

Security Considerations

When serving files to users, always validate:
  1. The resolved path is within allowed directories
  2. The user has permission to access the file
  3. The file hasn’t been tampered with (verify SHA-256)

Path Traversal Prevention

The service uses Path.normalize() and validates that resolved paths remain within the base path:
Path basePath = getBasePath();
Path resolved = basePath.resolve(relativePath).normalize();
if (!resolved.startsWith(basePath)) {
    throw new SecurityException("Path traversal attempt detected");
}

SHA-256 Hash Calculation

The service calculates SHA-256 hashes for file integrity verification: Method: calculateSha256(Path file) Process:
  1. Opens file input stream
  2. Creates SHA-256 MessageDigest
  3. Reads file in 8KB chunks
  4. Updates digest for each chunk
  5. Returns hex-encoded hash (lowercase)
Example Hash: a3f5d...b2c1e (64 characters)
Hashes are calculated after the file is written to disk to ensure the stored file’s integrity.

StoredFileInfo DTO

Returned by storePersonFile() with file metadata:
public class StoredFileInfo {
    private final String relativePath;  // Relative to basePath
    private final long size;            // Bytes
    private final String sha256;        // Hex hash (lowercase)
    private final String originalName;  // Original filename
    
    // Getters...
}
Usage Example:
StoredFileInfo info = fileStorageService.storePersonFile(person, file);

// Create FileRecord entity
FileRecord record = new FileRecord();
record.setStoragePath(info.getRelativePath());
record.setByteSize(info.getSize());
record.setHashSha256(info.getSha256());
record.setOriginalName(info.getOriginalName());
record.setStoredAs(FileStoredAs.PATH);

fileRecordRepository.save(record);
  • PersonService - Creates persons and triggers folder creation (FileStorageService:80)
  • SignedUrlService - Generates secure URLs for file access
  • PersonDocumentService - Links files to person documents

Error Handling

ExceptionCauseSolution
IllegalStateException: "No se pudo crear la carpeta de la persona"Filesystem permissions issueCheck write permissions on base path
IllegalStateException: "storagePath vacío para FileRecord"FileRecord has no pathEnsure storagePath is set before resolving
IllegalStateException: "Error calculando hash SHA-256"File read error or algorithm unavailableCheck file existence and Java crypto support
IllegalStateException: "Error guardando archivo en disco"I/O error during writeCheck disk space and permissions

Best Practices

  1. Always use relative paths when persisting to database
  2. Normalize paths using service methods, not manual string operations
  3. Validate file types before calling storePersonFile()
  4. Verify SHA-256 hashes when serving sensitive documents
  5. Use loadAsResource() instead of direct file access for HTTP responses

See Also

Build docs developers (and LLMs) love