Best way to delete files from FileStorage in a EventListener

Hi, forum followers
I’m trying to implement the deletion of files from FileStorage associated to an instance when this one is deleted.

Entity Anotacio has the attribute ‘fitxers’ as a many to many association with FileDescriptor[sys$FileDescriptor]

package com.company.logistica.entity;

import com.company.mestrelegacy.entity.Article;
import com.company.mestrelegacy.entity.Proces;
import com.company.mestrelegacy.entity.Proveidor;
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.cuba.core.entity.annotation.Lookup;
import com.haulmont.cuba.core.entity.annotation.LookupType;
import com.haulmont.cuba.core.entity.annotation.OnDelete;
import com.haulmont.cuba.core.entity.annotation.PublishEntityChangedEvents;
import com.haulmont.cuba.core.global.DeletePolicy;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;

@Table(name = "LOGIS_ANOTACIO")
@Entity(name = "logis_Anotacio")
@PublishEntityChangedEvents
public class Anotacio extends StandardEntity {
    private static final long serialVersionUID = -423410436120639202L;

    @Temporal(TemporalType.DATE)
    @NotNull
    @Column(name = "DATA_", nullable = false)
    private Date data;

    @OneToMany(mappedBy = "anotacio")
    private List<EtiquetaNota> etiquetes;

    @Lookup(type = LookupType.SCREEN, actions = {"lookup", "open", "clear"})
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CONTRACTE_LN_ID")
    private ContracteLin contracteLn;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ARTICLE_ID")
    private Article article;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PROCES_ID")
    private Proces proces;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PROVEIDOR_ID")
    private Proveidor proveidor;

    @Column(name = "PREU")
    private Double preu;

    @Column(name = "ASSUMPTE", length = 120)
    private String assumpte;

    @Lob
    @Column(name = "OBSERVACIONS")
    private String observacions;

    @JoinTable(name = "LOGIS_ANOTACIO_FILE_DESCRIPTOR_LINK",
            joinColumns = @JoinColumn(name = "ANOTACIO_ID"),
            inverseJoinColumns = @JoinColumn(name = "FILE_DESCRIPTOR_ID"))
    @OnDelete(DeletePolicy.CASCADE)
    @ManyToMany
    private List<FileDescriptor> fitxers;

    public List<FileDescriptor> getFitxers() {
        return fitxers;
    }

    public void setFitxers(List<FileDescriptor> fitxers) {
        this.fitxers = fitxers;
    }

    public List<EtiquetaNota> getEtiquetes() {
        return etiquetes;
    }

    public void setEtiquetes(List<EtiquetaNota> etiquetes) {
        this.etiquetes = etiquetes;
    }

    public String getObservacions() {
        return observacions;
    }

    public void setObservacions(String observacions) {
        this.observacions = observacions;
    }

    public String getAssumpte() {
        return assumpte;
    }

    public void setAssumpte(String assumpte) {
        this.assumpte = assumpte;
    }

    public Double getPreu() {
        return preu;
    }

    public void setPreu(Double preu) {
        this.preu = preu;
    }

    public Proveidor getProveidor() {
        return proveidor;
    }

    public void setProveidor(Proveidor proveidor) {
        this.proveidor = proveidor;
    }

    public Proces getProces() {
        return proces;
    }

    public void setProces(Proces proces) {
        this.proces = proces;
    }

    public Article getArticle() {
        return article;
    }

    public void setArticle(Article article) {
        this.article = article;
    }

    public ContracteLin getContracteLn() {
        return contracteLn;
    }

    public void setContracteLn(ContracteLin contracteLn) {
        this.contracteLn = contracteLn;
    }

    public Date getData() {
        return data;
    }

    public void setData(Date data) {
        this.data = data;
    }
}

The implementation for deleting all files associated to an Anotacio instance when this one is deleted is located on next Event Listener:

package com.company.logistica.listeners;

import com.company.logistica.entity.Anotacio;
import com.haulmont.cuba.core.TransactionalDataManager;
import com.haulmont.cuba.core.app.FileStorageService;
import com.haulmont.cuba.core.app.events.AttributeChanges;
import com.haulmont.cuba.core.app.events.EntityChangedEvent;
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.entity.contracts.Id;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.FileStorageException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

import javax.inject.Inject;
import java.util.Collection;
import org.slf4j.Logger;
import java.util.UUID;

@Component("logis_AnotacioChangedListener")
public class AnotacioChangedListener {

    @Inject
    private DataManager dataManager;
    @Inject
    private TransactionalDataManager transactionalDataManager;
    @Inject
    private FileStorageService fileStorageService;
    @Inject
    private Logger log;

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void beforeCommit(EntityChangedEvent<Anotacio, UUID> event) {
        AttributeChanges canvis = event.getChanges();
        if (event.getType().equals(EntityChangedEvent.Type.DELETED)){
            //Obtenir la llista de fitxers associats al registre Anotacio que s'ha eliminat però encara es troba a BD:
            Collection<Id<FileDescriptor, UUID>> fitxers = event.getChanges().getOldCollection("fitxers", FileDescriptor.class);
            for (Id<FileDescriptor, UUID> fitxerId : fitxers){
                FileDescriptor fitxer = transactionalDataManager.load(fitxerId).one();
                try{
                    //Eliminació del fitxer del FileStorage
                    fileStorageService.removeFile(fitxer);
                } catch (FileStorageException e){
                    String missatge = "Error a l'eliminar el fitxer";
                    missatge.concat(" : ".concat(fitxer.getName()));
                    log.error(missatge, e);
                }
            }
        }
    }
}

Application raise Exception:
java.lang.IllegalStateException: No results
error when tries to get the FileDescriptor with the following instruction: FileDescriptor fitxer = transactionalDataManager.load(fitxerId).one();
app.log (105.5 KB)

What I’m doing wrong for getting the FileDescriptor of each associated file to the instance?

Thanks in advance.
Xavier Lorente

Hi.
The problem is in @OnDelete(DeletePolicy.CASCADE) annotation. It marks FileDescriptor entities as deleted, thats why the TransactionalDataManager cannot load the entity. You need do to two things in order to solve the problem.
First, delete the @OnDelete(DeletePolicy.CASCADE) annotation.
Second, add FileDescriptor entity deletion into listener method. See the example here

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void beforeCommit(EntityChangedEvent<Demo, UUID> event) {
        if (event.getType().equals(EntityChangedEvent.Type.DELETED)) {
            Collection<Id<FileDescriptor, UUID>> files = event.getChanges().getOldCollection("files", FileDescriptor.class);
            for (Id<FileDescriptor, UUID> fileId : files) {
                FileDescriptor fitxer = transactionalDataManager.load(fileId).one();
                try {
                    transactionalDataManager.remove(fileId); // delete FileDescriptor entity
                    fileStorageAPI.removeFile(fitxer); // delete file
                } catch (FileStorageException e) {

                }
            }

        }
    }
1 Like

Thanks, Natalia, for your support.
You was right about deleting the * @OnDelete(DeletePolicy.CASCADE)* annotation and move the FileDescriptor entity deletion into de Listener event.
Following your indications all runs as it was expected.
Regards,

1 Like