EntityListener not affecting an instance in a nested graph in a StandardEditor

Hi,

I have registered an EntityListener for a class A, and B, being B down in the object graph having A as root in a StandardEditor.

When commiting changes, entitylistener for class A triggers correctly, but the one for B doesn’t.

From my understanding in the documentation it should fire aswell.

How can I address this?

Thanks for the help and regards.
Carlos.

If you commit changes in entity A on StandardEditor why should the related entity B be commited to?
The listener for commit will be called only for the entity that has attribute changes.

2 Likes

Hi Alex, thanks.

B is an A related entity, and is assigned from the same AEdit instance (StandardEdictor). When making a new assignment B is created from scratch, and attached to A, as a new instance. The B instance is present in the datacontext, and is persisted, therefore the BeforeEntytyInsert should be triggered, and it is not.

Any ideas?
Thanks and regards.

Carlos.

How are the data containers / data stores on the screen described?
What’s your data model?
“A” is an association one_to_many with “B”?
In this case, “A” does not store any data about “B.”
Entity “B” has an attribute where it stores the ID of the parent record “A.”
If you have an “association,” then “B.” is typically commited independently of “A.”
If “A” “composition” with “B” - they are stored and removed always together.
Different relationship models are well shown here.

It’s a relation 1-to-1.

I have discovered that if merging the B entity in the dataContext inside the onBeforeCommitChanges() method of the standard editor, it works, which confirms my suspicions. The B entity although being set inside the editor, was never marked as dirty and included in the dataContext.

The B instance is set via a CompositeComponent implementing the Field interface. Followed the guidelines in the documentation and it is working. The B entity is however stored in the database thanks to the CascadeType.ALL annotation for the B member of A, otherwhise it wouldn’t.

So I presume now the challenge is to properly code the CompositeComponent field in order for it to attach the newly created B entity in the editor’s dataContext. I have however essayed to do that, but the ContainerValueSource instance has only protected access to the dataContext instance, so I am not able to add the B entity. At this point is where my Cuba skills meet the borderline. I don’t know how should I properly, and in a cuba-fashion, attain that the B entity merges in the dataContet. I think I am missing something with the ContainerValueSource, but from its surface method definitions, I am not grasping what to do next.

Any ideas?

Attached the code of my CompositeComponent: please be patient I am learning. It is a field aiming at uploading a file and storing its byte representation in a B entity (ArchivoAdjunto entity in factArchivoAdjuntoField.java (9.2 KB) ).

Thanks for your attention and regards.

Carlos.

Can you create a small project where there is a model of your problem and it can be reproduced?
It is very difficult to advise something certain from disparate fragments.

Hi,

https://doc.cuba-platform.com/manual-7.2/entity_attr_annotations.html#manyToOne_annotation

The usage of JPA cascade annotation attribute is not recommended. The entities persisted and merged implicitly using such declaration will bypass some system mechanisms. In particular, … entity listeners are not invoked at all.

If you use JPA cascade persist - entity listeners won’t work.

Hi Alex,

tried that as well but still not working.

In the attached zip file find the entities. ContratoInquilino should be the owning entity (entity A from my simplified explanation), ArchivoAdjunto (entity B) and ArchivoAdjuntoEntityListener.

the curious thing is that the entity B, ArchivoAdjunto, although getting rid of the cascade persist, according to your advise, gets persisted. So great on that point. On the other hand the entity listener keeps not being called.

Thanks again.

Carlos.files.zip (5.8 KB)

Hi Carloscz25,

If you want to get more specific advices or if you think you’ve discovered a bug, please create a small demo project which reproduces the problem.
When having a small project, it will be easy for the CUBA team to suggest how to make the code work or admit the existence of a bug.

As I see, there are still some CascadeType.ALL left in the owning entity definition:

    @OnDeleteInverse(DeletePolicy.UNLINK)
    @OnDelete(DeletePolicy.UNLINK)
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "ARCHIVO_ADJUNTO_RENUNCIA_ID")
    protected ArchivoAdjunto archivoAdjuntoRenuncia;
1 Like

Hi Alex,

thanks for your answer. I tried your suggestion but didn’t get it to work. Would like to reproduce it in a separate project. I am pretty sure it has to do with some faulty setting in a custom field I use in the editor.

I will try to reproduce the problem in a separate project and come back with it.

Can you explain me how to recover the zipProject task. I don’t know at which point it disappeared from the Gradle view in CUBA Studio and don’t know how to get it back. Reimport gradle project?? I haven’t found it on the internet either. It wouldn’t disturb having the code of the task in the documentation entry, then it is short enough.

thanks and regards.
Carlos.

You can find it in the tree of tasks in the right Gradle tool window:
image

Hi Alex,

man sorry for that. However I must point out that the search function in the gradle view, does sometimes find the items and sometimes not, and zipProject was one of those, at least in my case.

Thanks. Will prepare a project asap and come back to you.

Regards,

Carlos Conti.

Hi Alex,

there you have a minimal representation of my project. AS you will see entity B’s listener onBeforeCommit is not called upon commiting changes of a A StandardEditor, within which there’s a custom field for entity B. The code is pretty much the same as the code I have in my project.

I am pretty sure there’s something wrong in my custom field, hence not calling the listener, however the entity and the associations persist correctly.

Looking forward to hearing from you.

Regards,
Carlos.
testentitylisteners.zip (1.3 MB)

Hi, Carlos

Yeah, that’s your code problem.
You’re creating a new instance of B using just this: new B();
In doing so, you get a new instance that has nothing to do with the data context.
It is stored in the DB with main entity only because there is a ratio of 1 to 1 in the main entity.
You need to merge a new instance B to the data context before commit.
If you skip other screen scenarios (whether creating a new one or editing an entity), add 1 line.
Then BChangedListener 'll work.

@Subscribe
    public void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
        A a = this.getEditedEntity();
        dataContext.merge(a.getB()); //add to data context
    }

And it is not recommended to use simply new MyEntity() to create a new instance.
Recommended use of metadata.create (MyEntity.class)

Hi Alex,

thanks for this conversation it is being really helpful. I am gaining good understanding on how things work in the background.

I will check metadata.create() to see if it means any change on my results. Even if not I will leave it like that following your recommendation.

I can’t adopt this solution, applying the onBeforeCommitChanges() on each screen is not viable due to the big number of them. Besides I would have to manage every B subinstance manually. Therefore I cannot afford that. I need to include that logic, merging in the dataContext, within the field logic.

To attain that I tried to get access to the screen dataContext from inside the field. But didn’t manage to do it. Does metada.create() help in that sense? At some point of code in the CUba Platform this needs to be done that way, then for the rest of the widgets the behavior is as expected.

Any ideas?

Carlos.

Hi,
I’ve checked your sample.
You can achieve all your goals by changing the code of your WebBField component the following way:

// Obtain DataContext of enclosing screen in onShowUploadDialogButtonClick()
DataContext dataContext = UiControllerUtils.getScreenData(getFrame().getFrameOwner()).getDataContext();

// Change how B is created:
//B aa = new B();
B aa = dataContext.create(B.class);

This way - all new B instances will be registered in data context and saved correctly.

Nice tip Alex. It works.

Mark this thread as solved.

thank you very much.

Carlos.