This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

Entity Listener

Entity listener are used to make changes to entities or run logic when they get created, updated or deleted before the database transaction is committed (except CollectingAfterCommitListener). Listeners listen to events (create, update, delete) of defined entities. A listener is registered on at least one entity model. It is possible to register the same listener on multiple entity models.

Listeners are hivemind services which are contributed to the configuration point nice2.persist.core.EntityListeners.

See Listeners for a more technical documentation of the listener.

Warning

Do not use Entity Listeners if the goal also can be reached by Default-Values

Dependencies

Adding entity listeners requires the following dependency in the pom.xml of the impl module.

<dependency>
  <groupId>ch.tocco.nice2.persist.core</groupId>
  <artifactId>nice2-persist-core-api</artifactId>
  <version>1.0-SNAPSHOT</version>
  <type>jar</type>
  <scope>provided</scope>
</dependency>

Additionally the module has to be imported in the file hivemodule.xml

<contribution configuration-id="hiveapp.ClassLoader">
  <import feature="ch.tocco.nice2.persist" version="*"/>
</contribution>

CollectingEntityListener

Collecting entity listeners fetch all EntityFacadeEvent until the current transaction is about to commit. This means a collecting entity listener is executed only once right before the commit. All EntityFacadeEvents which are produced later, for example by an other collecting entity listener which runs after the current, are discarded.

To add a CollectingEntityListener add a Java class which extends the class CollectingEntityListener. Add the class in the impl module where you working on. The method onBeforeCommit must be overwritten. This is the method which gets called by the nice2 framework just before the commit takes place.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        // your code goes here
    }
}

The created listener must be registered as hivemind service in the file hivemodule.xml.

<service-point id="MyCollectingEntityListener" interface="ch.tocco.nice2.persist.entity.events.EntityFacadeListener">
  <invoke-factory model="threaded">
    <construct class="ch.tocco.nice2.path.to.entitylistener.MyCollectingEntityListener"/>
  </invoke-factory>
</service-point>

Important

The service model must be threaded on collecting entity listeners (model="threaded"). This means that the service is instantiated once per Thread and bound to this Thread only. This is needed because the collecting entity listener fetches every EntityFacadeEvent of one transaction only. For more information about hivemind service models see Threaded Service Model.

Now the service needs to be contributed as Listener. With the contribution also the entity model on which the listener should listen must be defined. The example below shows how the registered listener MyCollectingEntityListener can be contributed as listener.

<contribution configuration-id="nice2.persist.core.EntityListeners">
  <listener listener="service:MyCollectingEntityListener" filter="User"/>
</contribution>
<listener/>

Attribute

Description

listener

The service which extends the class CollectingEntityListener.

filter

A comma separated list of entity models which the listener should listen to.

Handle Events

The class CollectingEntityListener provides some useful methods to handle the EntityFacadeEvents correctly. The most important methods are described here. Open the class ch.tocco.nice2.persist.util.CollectingEntityListener to see all methods.

Get all Created Entities

The method getCreatedEntities returns all created entities with the entity model which the listener was contributed to listen to.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getCreatedEntities().forEach(entity -> {
            // Your code goes here
        });
    }
}

Get all Updated Entities

The method getUpdatedEntities returns all updated entities with the entity model which the listener was contributed to listen to.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getUpdatedEntities().forEach(entity -> {
            // Your code goes here
        });
    }
}

Get all Deleted Entities

The method getDeletedEntities returns all deleted entities with the entity model which the listener was contributed to listen to.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getDeletedEntities().forEach(entity -> {
            // Your code goes here
        });
    }
}

Get Affected Entities

The method getAffectedEntities returns all created and updated entities with the entity model which the listener was contributed to listen to.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getAffectedEntities().forEach(entity -> {
            // Your code goes here
        });
    }
}

Get All Affected Entities

The method getAllAffectedEntities returns all created, updated and deleted entities with the entity model which the listener was contributed to listen to.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getAllAffectedEntities().forEach(entity -> {
            // Your code goes here
        });
    }
}

Using the Context in Collecting Entity Listeners

The context can be received by the Transaction passed to the onBeforeCommit method.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        Context context = tx.getContext();
        // Your code goes here
    }
}

Important

Do not inject the Context in a CollectingEntityListener but get it from the passed Transaction

The Order of Collecting Entity Listeners

It is not possible to define a specific order in which the collecting entity listeners are processed. This means collecting entity listeners could behave differently depending on the order they run. The next image shows how the result can be different if two collecting entity listeners run in different order.

../../_images/listener-collecting-entity-listener-order.png

In the left example the first listener creates a second user entity if a user was created before. The second listener creates an address for each created user. This results in two users and two addresses.

In the right example the first listener creates an address entity. After the first listener is finished, one new created user and one new created address entity exists. The second listener creates a new user entity but does nothing with the newly created address from the first listener. Because collecting entity listeners only run once per transaction the first listener does not process the user created by the second listener.

InterruptibleEntityFacadeAdapter

An InterruptibleEntityFacadeAdapter is executed for every EntityFacadeEvent which belongs to the entity on which the listener is contributed to listen to. This means every time setValue or a similar method is called on the entity the InterruptibleEntityFacadeAdapter is executed.

Note

The InterruptibleEntityFacadeAdapter is almost the same as the EntityFacadeListener but handles rolled-back transactions and InterruptedExceptions itself. Always use the InterruptibleEntityFacadeAdapter if there is not a good reason to not use it.

To add a InterruptibleEntityFacadeAdapter add a Java class which extends the class. Add the class in the impl module where you working on.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {}

The listener needs to be registered as hivemind service in the file hivemodule.xml.

<service-point id="MyEntityFacadeAdapter" interface="ch.tocco.nice2.persist.entity.events.EntityFacadeListener">
  <invoke-factory>
    <construct class="ch.tocco.nice2.path.to.MyEntityFacadeAdapter"/>
  </invoke-factory>
</service-point>

Now the service needs to be contributed as Listener. With the contribution also the entity model on which the listener should listen to must be defined. The example below shows how the registered listener MyEntityFacadeAdapter can be contributed as listener.

<contribution configuration-id="nice2.persist.core.EntityListeners">
  <listener listener="service:MyEntityFacadeAdapter" filter="User"/>
</contribution>
<listener/>

Attribute

Description

listener

The service which extends the class CollectingEntityListener.

filter

A comma separated list of entity models which the listener should listen to.

Now depending on what the listener needs to do there are several methods which can be overridden.

entityCreatingInterruptible

This method gets called if a new entity was created.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityCreatingInterruptible(EntityFacadeEvent event) throws InterruptedException {
        Entity user = event.getSource();
        // do something with `user`
    }
}

entityDeletingInterruptible

This method gets called if an entity was deleted.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityDeletingInterruptible(EntityFacadeEvent event) throws InterruptedException {
        Entity user = event.getSource();
        // do something with `user`
    }
}

entityChangingInterruptible

This method gets called if any changes are made to an entity.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityChangingInterruptible(EntityChangedEvent event) throws InterruptedException {
        if ("field_name".equals(event.getField().getName())) {
            // do something with `user`
        }
   }
}

EntityChangedEvent

It is important to only process the listener if it is really necessary. Lets say a listener must set a flag isAdult on Users if they’re older than 18 years. This could be done like this:

// Bad example
public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityChangingInterruptible(EntityChangedEvent event) throws InterruptedException {
        Entity user = event.getSource();
        if (isAdult(user) {
            user.setValue("is_adult", true);
        }
    }
}

This would work without any problems. But most probably this listener would be executed a lot of times even it would not be necessary. Because InterruptibleEntityFacadeAdapters are executed every time setValue is called on the entity, this listener is also executed if for example only the name of the user was changed. The name has nothing to do with the age of user.

A EntityChangedEvent is passed to the method entityChangingInterruptible which has some additional methods over the EntityFacadeEvent to work with. The above example can be rewritten to the following:

// Good example
public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityChangingInterruptible(EntityChangedEvent event) throws InterruptedException {
        if ("birthdate".equals(event.getField().getName())) {
           LocaleDate birthdate = (LocalDate) event.getNewValue();
           if (isAdult(birthdate) {
               user.setValue("is_adult", true);
           }
        }
    }
}

Before the whole logic (setting the adult flag) is processed, we check if the change which is done to the entity belongs to the field brithdate because this is the only field which is relevant for this listener. Then instead of reading the field birthdate from the entity we just call the method getNewValue on the A EntityChangedEvent. Because getNewValue returns an Object it needs to be casted first.

entityRelationChangingInterruptible

This method gets called if a relation on the entity was changed.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityRelationChangingInterruptible(EntityRelationChangedEvent event) throws InterruptedException {
        if("relRelation_name".equals(event.getRelation().getName())) {
            // Your code goes here
        }
    }
}

EntityRelationChangedEvent

A EntityRelationChangedEvent is passed to the method entityRelationChangingInterruptible which has some additional methods over the EntityFacadeEvent to work with.

To check what relation was changed the method getRelation can get called on the EntityRelationChangedEvent.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityRelationChangingInterruptible(EntityRelationChangedEvent event) throws InterruptedException {
        Relation relation = event.getRelation();
        String relationName = relation.getName(); // e.g. `relUser`
    }
}

There also methods to check how the relation got changed.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityRelationChangingInterruptible(EntityRelationChangedEvent event) throws InterruptedException {
        if (event.isAdded()) {
            // code is executed when the relation was added to the entity.
            // E.g. User.relUser_status was set to `active`
        }

        if (event.isRemoved()) {
           // code is executed when the relation was removed.
           // E.g. User.relUser_status was cleared
        }

        if (event.isAdjusting()) {
           // code is executed if the relation was changed.
           // E.g. User.relUser_status was changed from `active` to `archived`
        }
    }
}

Using the Context in Entity Facade Listeners

The context can be received by the EntityFacadeEvent passed to the overwritten methods.

public class MyEntityFacadeAdapter extends InterruptibleEntityFacadeAdapter {
    @Override
    public void entityChangingInterruptible(EntityChangedEvent event) throws InterruptedException {
        Context context = event.getSource().getContext();
    }
}

Important

Do not inject the Context in a InterruptibleEntityFacadeAdapter but get it from the source entity.

Avoid Infinite Loops

With InterruptibleEntityFacadeAdapters it is possible to create infinite loops. Because these listeners are executed every time a change has made to the entity which the listener listens to. In the picture below an example of an infinite loop is shown (example does not make any sense).

../../_images/listener-entity-facade-listener-infinite-loop.png

Both listeners listen to changes on the entity User. Listener A listens on changes on the field firstname and sets a value on the field lastname. Listener B listens to the field lastname which was set from the listener A and sets a value on the field firstname. Now listener A again is executed and so on.

CollectingAfterCommitListener

CollectingAfterCommitListeners are fired after the transaction was committed. This can be useful if something only must be done if something else was persisted before. For example mails are sent often with CollectingAfterCommitListeners. Lets say a user should receive an e-mail if he was registered to an event. This could be done within a InterruptibleEntityFacadeAdapter or CollectingEntityListener.

public class MyCollectingEntityListener extends CollectingEntityListener {
    @Override
    public void onBeforeCommit(Transaction tx) {
        getCreatedEntities().forEach(registration -> {
            sendMailTo(registration); // what if later in an other entity listener something goes wrong?
        });
    }
}

But if the current transaction for some reason fails, it will be rolled back and the registration entity is not persisted. In this case the user would have received an e-mail but was not actually registered to the event.

That is when CollectingAfterCommitListeners are useful.

CollectingAfterCommitListeners need to be registered and contributed as listener the same way as CollectingEntityListener are.

<service-point id="MyAfterCollectingEntityListener" interface="ch.tocco.nice2.persist.entity.events.EntityListener">
  <invoke-factory model="threaded">
    <construct class="ch.tocco.nice2.path.to.entitylistener.MyAfterCollectingEntityListener"/>
  </invoke-factory>
</service-point>
<contribution configuration-id="nice2.persist.core.EntityListeners">
  <listener listener="service:MyAfterCollectingEntityListener" filter="Registration"/>
</contribution>

The listener must extend the class CollectingAfterCommitListener and overwrite the method getAfterCommitTask which returns an AfterCommitTask.

public class MyCollectingAfterCommitListener extends CollectingAfterCommitListener {
    public MyEntityFacadeAdapter(CommandExecutor commandExecutor) {
        super(commandExecutor);
    }

    @Override
    protected AfterCommitTask getAfterCommitTask() {
        return new AfterCommitTask() {
            @Override
            public void onAfterCommit(CommandContext commandContext) throws Exception {
                // Your code goes here
            }
        };
    }
}

Note

A CollectingAfterCommitListener does not know what has changed on the entities itself. But if the entity got created, updated or deleted is known.