Uploading and saving files

The FileReference entity

FileManagerModule provides a FileReference entity to handle file upload and the actual saving of a physical file. When saving a file the MultipartFileToFileReferenceConverter is used to convert the file into a FileReference. When not overridden the MultipartFileToFileReferenceConverter uses the default FileRepository to persist the files trough the FileReferenceService. If you want to customize the FileRepository that is used you can read the section about changing the default FileRegistry for saving a FileReference.

When saving a FileReference the physical file always get’s persisted in temp storage by default despite of validation errors. This is so the user doesn’t have to re-upload the file if there are other validation errors.

Single file upload

An example for saving a file and linking it to an Entity.

Example storing a file
@Entity
@Table(name = "test_invoice")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Invoice extends SettableIdBasedEntity<Invoice> {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "seq_test_invoice_id")
    @GenericGenerator(
            name = "seq_test_invoice_id",
            strategy = AcrossSequenceGenerator.STRATEGY,
            parameters = {
                    @org.hibernate.annotations.Parameter(name = "sequenceName", value = "seq_test_invoice_id"),
                    @org.hibernate.annotations.Parameter(name = "allocationSize", value = "1")
            }
    )
    private Long id;

    @NotBlank
    @Length(max = 200)
    @Column(name = "name")
    private String name;

    @NotNull
    @ManyToOne
    @JoinColumn(name = "attachment_id", referencedColumnName = "id")
    private FileReference attachment;

}

Uploading multiple files

The FileReference entity can be used to handle multi-file-uploads as well.

Example storing a file
@Entity
@Table(name = "test_expense")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Expense extends SettableIdBasedEntity<Expense> {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "seq_test_expense_id")
    @GenericGenerator(
            name = "seq_test_expense_id",
            strategy = AcrossSequenceGenerator.STRATEGY,
            parameters = {
                    @org.hibernate.annotations.Parameter(name = "sequenceName", value = "seq_test_expense_id"),
                    @org.hibernate.annotations.Parameter(name = "allocationSize", value = "1")
            }
    )
    private Long id;

    @NotBlank
    @Length(max = 200)
    @Column(name = "name")
    private String name;

    @NotNull
    @NotEmpty
    @ManyToMany
    @Setter(AccessLevel.NONE)
    @JoinTable(
            name = "expense_attachments",
            joinColumns = @JoinColumn(name = "fr_expense_expense_id"),
            inverseJoinColumns = @JoinColumn(name = "fr_expense_fr_id")
    )
    private List<FileReference> attachments;

    public void setAttachments( List<FileReference> attachments ) {
        this.attachments = attachments.stream().filter( Objects::nonNull ).collect( Collectors.toList() );
    }
}

By default when saving a FileReference the default FileRepository is being used. The example below shows how the physical file can be moved to a different FileRepository when there are no validation errors and the entity is saved. This can configured by overriding the saveConsumer of your entity.

Change the FileRepository to use on save
@Configuration
@RequiredArgsConstructor
public class ExpenseEntityConfiguration implements EntityConfigurer {
    private final FileReferenceService fileReferenceService;

    @Override
    public void configure(EntitiesConfigurationBuilder entities) {
        entities.withType(Expense.class)
                .properties(
                        props -> props
                                .property("attachments[]")
                                .controller(ctl -> ctl
                                        .withBindingContext(FileReference.class)
                                        .saveConsumer(
                                                (ctx, fr) -> fileReferenceService
                                                        .changeFileRepository(fr.getNewValue(), "permanent", true)
                                        )
                                )
                );
    }
}

Change the default FileRepository used for persisting a FileReference

You can override the FileRepository that is used to persist the physical files that are represented by a FileReference.

Change the FileRepository that will be used when saving a FileReference
@Configuration
public class DefaultFileReferenceFileRepository {

    @Autowired
    public void changeDefaultFileRepositoryForFileReferences(MultipartFileToFileReferenceConverter multipartFileToFileReferenceConverter) {
        multipartFileToFileReferenceConverter.setRepositoryId("demo");
    }
}

Manually registering a FileReference property

You can manually register a FileReference property on an entity if you don’t want to use the default way of saving the FileReference with hibernate.

The person entity with a photoId
@Entity
@Table(name = "test_person")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Person extends SettableIdBasedEntity<Person> {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "seq_test_person_id")
    @GenericGenerator(
            name = "seq_test_person_id",
            strategy = AcrossSequenceGenerator.STRATEGY,
            parameters = {
                    @org.hibernate.annotations.Parameter(name = "sequenceName", value = "seq_test_person_id"),
                    @org.hibernate.annotations.Parameter(name = "allocationSize", value = "1")
            }
    )
    private Long id;

    @NotBlank
    @Length(max = 200)
    @Column(name = "name")
    private String name;

    @Column(name = "photo_id")
    private Long photoId;
}

There is only a photoId defined to hold the fileReferenceId. We use an EntityConfigurer to hide that property and create a custom photo property with FileReference as propertyType.

Add a custom FileReference property to an entity
@Configuration
@RequiredArgsConstructor
public class PersonEntityConfiguration implements EntityConfigurer {
    private final FileReferenceRepository fileReferenceRepository;

    @Override
    public void configure(EntitiesConfigurationBuilder entities) {
        entities.withType(Person.class)
                .properties(
                        props -> props
                                .property("photoId").and()
                                .property("photo")
                                .propertyType(FileReference.class)
                                .displayName("Photo")
                                .readable(true)
                                .writable(true)
                                .hidden(false)
                                .controller( c -> c.withTarget( Person.class, FileReference.class )
                                        .valueFetcher( person -> person.getPhotoId() != null ? fileReferenceRepository
                                                .findOne( person.getPhotoId() ) : null )
                                        .applyValueConsumer(
                                                ( person, fileReference ) -> {
                                                    if ( fileReference.getNewValue() != null && !fileReference.isDeleted() ) {
                                                        person.setPhotoId( fileReference.getNewValue().getId() );
                                                    }
                                                    else {
                                                        person.setPhotoId( null );
                                                    }
                                                } )
                                )
                                .attribute(EntityAttributes.FORM_ENCTYPE, FormViewElement.ENCTYPE_MULTIPART)
                );
    }
}

For this example we hide the photoId property in favor of our custom created photo property. Our custom photo property has a FileReference as propertyType and will implement the controller method to handle the correct saving and showing of our photo file. The valueFetcher in the controller method is used to transform the photoId into a FileReference. The applyValueConsumer on the other hand is used to set the photoId on save.

By default when using a FileReference the right FORM_ENCTYPE is set. If you are using FileReference as a custom property you have to set the FORM_ENCTYPE to ENCTYPE_MULTIPART yourself.