4 Application Wide Messages

Start Cloud IDE

Start Cloud IDE (Start Events)

Up until now, we have learned that beans managed by a container can be linked with each other using CDI. However, beans are not the only thing for which this applies: the principle of loose coupling of components with CDI goes one step further, as during runtime, components can send and receive any message the user wishes. The components no longer have dependencies to each other, but rather solely to the class that contains the message. This kind of loose coupling enables additional receiving components to be added without the sending component having to be modified. This improves the extensibility and maintainability of the application.

4.1 Sending and Receiving Events

As detailed in section 2.3, a troublesome dependency exists from the EditCampaignController to the CampaignListProducer bean. By having the EditCampaignController send a message whenever a campaign is added, CDI offers a simple way to remove this dependency. The CampaignListProducer can then respond to this message and, since it manages the list of all Campaign objects, can add the new Campaign object to this list.

We will begin by changing the EditCampaignController such that when a campaign is added, a corresponding message is sent. The crucial element here is the message we will send: since we want a Campaign object to be added, it is sufficient – for the time being – to simply send this as the message. The message to be sent is therefore of the type Campaign. Since CDI sends messages via the generic class Event from the package javax.enterprise.event, Campaign must be set as the type parameter for this class. To achieve this, we must inject an attribute of the type Event<Campaign> into the bean by adding the following lines of code:

@Inject
private Event<Campaign> campaignAddEvent;

The corresponding message must now be sent in the method doSave instead of the bean CampaignListProducer being directly accessed. To do this, the following line:

campaignListProducer.getCampaigns().add(campaignProducer.getSelectedCampaign());

must be replaced by

campaignAddEvent.fire(campaignProducer.getSelectedCampaign());

Using the fire method of the Event object, the actual message of the type Campaign will now be sent.

Since the dependency to the CampaignListProducer is no longer required – which is a good thing for our application – the following lines can now be deleted:

@Inject
private CampaignListProducer campaignListProducer;

Now, the bean EditCampaignController will send the new Campaign object as a message whenever a new campaign is added. Other beans can now receive this message as desired. To enable this to happen, the receiving bean must implement a method that has a Campaign object as its only parameter. The parameter must have the annotation @Observes from the package javax.enterprise.event.

Currently, the message is only of interest for the CampaignListProducer. In accordance with the above, this bean must therefore implement the following method:

public void onCampaignAdded(@Observes Campaign campaign) {
    getCampaigns().add(campaign);
}

This method is now invoked synchronously by the container as soon as the EditCampaignController sends the message regarding the adding of a Campaign object. The container passes the Campaign object to be added as a parameter. In the example, this is added to the list of Campaign objects using the method add.

4.2 Differentiate Between Messages of the Same Type Using Qualifiers

Following the same principle as with the message for a new campaign, we can now send a message about the deletion of a campaign. To do this, we must modify the bean ListCampaignsController with an Event object that sends a message of the type Campaign:

@Inject
private Event<Campaign> campaignDeleteEvent;

To ensure that the message is sent, the fire method of the Event object must be passed as a parameter with the campaign to be deleted. To achieve this, the method commitDeleteCampaign must be replaced with the following:

public void commitDeleteCampaign() {
    campaignDeleteEvent.fire(campaignToDelete);
}

If we start the program now, we are faced with the problem that CDI cannot differentiate between the messages for the adding and deletion of a campaign. CDI differentiates between messages on the basis of their type; in this example, the type is the same for both events. The result is that the CampaignListProducer adds a Campaign object in both cases: when a campaign is actually being added (as we want it to), but also when a campaign is being deleted. This means that when a user deletes an object, it ends up being added to the list once more instead. Please have a try yourself.

To differentiate between the two messages, we require a further central concept of CDI: a so-called qualifier.

A qualifier is a user-defined annotation that serves to resolve ambiguities. In this specific case, we need two qualifiers to differentiate between our two different messages. These are specified using the names Added and Deleted in the class Events belonging to the package press.turngeek.mycampaign.util (see Listing 4-1).

package press.turngeek.mycampaign.util;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

public class Events {

    @Qualifier
    @Target({ FIELD, PARAMETER })
    @Retention(RUNTIME)
    public @interface Added {
    }

    @Qualifier
    @Target({ FIELD, PARAMETER })
    @Retention(RUNTIME)
    public @interface Deleted {
    }

}

Listing 4-1 Events class

Both annotations have been given the annotation @Qualifier from the package javax.inject; likewise, both are valid for attributes and parameters, a fact that is indicated by the annotation @Target.

To enable CDI to differentiate between the two messages, the Event object campaignAddEvent in EditCampaignController must now be given the annotation @Added:

@Inject 
@Added
private Event<Campaign> campaignAddEvent;

Similarly, the Event object campaignDeleteEvent in ListCampaignsController must be given the annotation @Deleted:

@Inject 
@Deleted
private Event<Campaign> campaignDeleteEvent;

As a result of this change, CDI can now differentiate between the messages in spite of them being of the same type.

To enable the application to react based on the message, the CampaignListProducer must be modified. This is done in two steps: firstly, the existing method with the annotation @Observes is modified with the qualifier @Added:

public void onCampaignAdded(@Observes @Added Campaign campaign) {
    getCampaigns().add(campaign);
}

…and then a similar method is added in the same bean to respond to the message with the qualifier @Deleted:

public void onCampaignDeleted(@Observes @Deleted Campaign campaign) {
    getCampaigns().remove(campaign);
}

This method simply removes the passed Campaign object from the list of Campaign objects by invoking the method remove. Incidentally, this represents an extension of the application’s functionality; now, when the user presses the button to confirm the deletion of a campaign, it will be removed from the list of campaigns.

Fig. 4-1 shows the relationships between the beans and the qualifier.

Relationships between the classes involved in the sending and receiving of messages
Fig. 4-1 Relationships between the classes involved in the sending and receiving of messages

Before continuing, you may know compare your workspace with the provided link:

Start Cloud IDE

Start Cloud IDE (End Events)

Discussion

Use the message board below to give the authors your feedback or to discuss this page’s topic with other readers (in English please!). Please don’t expect the authors to answer directly, but they might update the content of this site according to your feedback.