How to create CDI events

This tutorial by Rhuan Rocha, the author of Java EE 8 Design Patterns and Best Practices, will show you the implementation of an event in an asynchronous CDI.

Imagine you want to create an application that makes it possible to upload three types of files: ZIP, JPG, and PDF. Depending on the extension received at the request, it is intended that one event is launched and one observer will save its file on a disk using an asynchronous process. Each extension will have an observer, which will have an algorithm, making it possible to save the file on a disk. To develop this example, you have the following classes:

  • FileUploadResource: This is a class that represents the resource that receives all the requests in order to upload and launches respective events according to the file extension.
  • FileEvent: This is a bean that contains the file data and is sent to an event.
  • FileHandler: This is an interface of all the observers. In this example, all classes that react to FileEvent need to implement FileHandler.
  • JpgHandler: This is an implementation of FileHandler that saves a JPG file on a disk. This class is an observer that reacts with FileEvent launched to a JPG file.
  • PdfHandler: This is an implementation of FileHandler that saves a PDF file on a disk. This class is an observer that reacts with FileEvent launched to a PDF file.
  • ZipHandler: This is an implementation of FileHandler that saves a ZIP file on a disk. This class is an observer that reacts with FileEvent launched to a ZIP file.
  • Jpg: This is a qualifier used to establish that the JpgHandler observers need to react to an event.
  • Pdf: This is a qualifier used to establish that the PdfHandler observer needs to react to an event.
  • Zip: This is a qualifier used to establish that the ZipHandler observer needs to react to an event.

FileSystemUtils: This is a utility class to treat issues on a filesystem.

Implementing the FileUploadResource class

FileUploadResource is a resource class that uses JAX-RS to create a RESTful service to upload a file with JPG, PDF, and ZIP extensions.

FileEvent is a bean that is sent to the event—the observers will receive this:


public class FileEvent {
private File file;
private String mimeType;
public FileEvent() {
}
public FileEvent(File file, String mimeType) {
this.file = file;
this.mimeType = mimeType;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}

view raw

FileEvent.java

hosted with ❤ by GitHub

Qualifier to select the JpgHandler observer to react to an event

The following code has the Jpg qualifier used to define the correct handler:


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
public @interface Jpg {
}

view raw

Jpg.java

hosted with ❤ by GitHub

Qualifier to select the PdfHandler observer to react to an event

The following code has the Pdf qualifier used to define the correct handler:


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
public @interface Pdf {
}

view raw

Pdf.java

hosted with ❤ by GitHub

Qualifier to select the ZipHandler observer to react to an event

The following code has the Zip qualifier used to define the correct handler:


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
public @interface Zip {
}

view raw

Zip.java

hosted with ❤ by GitHub

The FileUploadResource class

In the following code block, you have theJFileUploadResource class, which uses JAX-RS and is a REST service:


@Path("upload")
public class FileUploadResource {
@Inject
Event<FileEvent> fileEvent;
@Consumes("application/pdf")
@POST
public Response uploadPdf(File file) {
FileEvent fileEvent = new FileEvent(file, "pdf");
Event<FileEvent> pdfEvent = this.fileEvent.select(new AnnotationLiteral<Pdf>() {
});
pdfEvent.fireAsync(fileEvent)
.whenCompleteAsync((event, err) -> {
if (Objects.isNull(err)) {
System.out.println("PDF saved");
} else {
err.printStackTrace();
}
});
return Response.ok().build();
}
@Consumes("image/jpeg")
@POST
public Response uploadJpg(File file) {
FileEvent fileEvent = new FileEvent(file, "jpg");
Event<FileEvent> jpgEvent = this.fileEvent.select(new AnnotationLiteral<Jpg>() {
});
jpgEvent.fireAsync(fileEvent)
.whenCompleteAsync((event, err) -> {
if (Objects.isNull(err)) {
System.out.println("JPG saved");
} else {
err.printStackTrace();
}
});
return Response.ok().build();
}
@Consumes("application/zip")
@POST
public Response uploadZip(File file) {
FileEvent fileEvent = new FileEvent(file, "zip");
Event<FileEvent> zipEvent = this.fileEvent.select(new AnnotationLiteral<Zip>() {
});
zipEvent.fireAsync(fileEvent)
.whenCompleteAsync((event, err) -> {
if (Objects.isNull(err)) {
System.out.println("PDF saved");
} else {
err.printStackTrace();
}
});
return Response.ok().build();
}
}

The preceding code contains the uploadPdf(File file), uploadJpg(File file), and uploadZip(File file) methods, which are called when a user wants to upload a file with PDF, JPG, or ZIP extensions, respectively. Furthermore, this class has the fileEvent attribute of the Event<FileEvent> type. Event<FileEvent> is the class responsible for launching an event driven by a qualifier. The following code snippet selects a correct Event, using an annotation as a qualifier:

Event<FileEvent> zipEvent = this.fileEvent
            .select(new AnnotationLiteral<Zip>() {});

Another way to establish the correct event to launch is to use the qualifier at the point where the object is injected using @Inject. However, this makes the event static and all events launched by the Event object are converted to the same type. Using the select (Annotation… var) method, you can launch a dynamic event as well as other event types. The following is an example of Event with a static event type:

@Inject
@Pdf //Qualifier
Event<FileEvent> pdfEvent;

Here, pdfEvent will always launch an event to an observer that processes an event marked by the @Pdf qualifier.

To launch an asynchronous event, you need to call the fireAsync(U var) method, which returns CompletionStage. In the following code block, you have a snippet that calls this method and prepares a callback function to execute when the process is complete:

zipEvent.fireAsync(fileEvent)
               .whenCompleteAsync( (event, err)->{
                   if( Objects.isNull( err ) )
                       System.out.println( "PDF saved" );
                   else
                       err.printStackTrace();
});

Implementing observers

When an event is launched, some elements will react to this event and process a task with the data given on the event. These elements are called observers and work as observer patterns, which create a one-to-many relationship between objects.

This occurs when one object is the subject and the other objects are the observers. Then, when the subject object is updated, all observer objects that are related to this subject-object are updated too.

CDI has a mechanism for creating observers that will react with its events. In this example, you’ll launch an event, create observers to react to these events, and process a task. To do so, you need to create handlers that represent the observer and process tasks. These handlers will be classes that implement the FileHandler interface as well as methods, called handle(FileEvent file). Note that the parameter of the handler(FileEvent file) method is a FileEvent type. This is the same type of data as sent to the event above. Here’s the code for the FileHandler interface and its implementations:


public interface FileHandler {
public void handle( FileEvent file ) throws IOException;
}

Here’s the code for the JpgHandler class, which is an implementation of FileHandler. This is responsible for saving the JPG files on a filesystem. This observer is called when an event is launched to the @Jpg qualifier:


public class JpgHandler implements FileHandler {
@Override
public void handle(@ObservesAsync @Jpg FileEvent file) throws
IOException {
FileSystemUtils.save( file.getFile(),"jpg","jpg_"+ new Date().getTime() + ".jpg" );
}
}

view raw

JpgHandler.java

hosted with ❤ by GitHub

In the above code block, you have the method handler, which has an @ObservesAsync annotation as well as @Jpg. This is a CDI annotation that configures this method to observe the FileEvent file, as well as the Qualifier, to configure the observer to react only to the event launched to the @Jpg qualifier.

The following code block has thePdfHandler class, which is an implementation of FileHandler responsible for persisting PDF files on a filesystem. This observer is called when an event is launched to a @Pdf qualifier:


public class PdfHandler implements FileHandler {
@Override
public void handle(@ObservesAsync @Pdf FileEvent file) throws
IOException {
FileSystemUtils.save( file.getFile(),"pdf","pdf_"+ new Date().getTime() + ".pdf" );
}
}

view raw

PdfHandler.java

hosted with ❤ by GitHub

In the above code, you have a handler method with an @ObservesAsync annotation as well as a @Pdf. This is a CDI annotation that configures the handle(FileEvent file) method to observe the FileEvent file and the Qualifier to configure the observer to react only to an event launched to the @Pdf qualifier.

The following code has the ZipHandler class, which is an implementation of FileHandler responsible for saving ZIP files on a filesystem. This observer is called when an event is launched to a @Zip qualifier:


public class ZipHandler implements FileHandler {
@Override
public void handle(@ObservesAsync @Zip FileEvent file) throws
IOException {
FileSystemUtils.save( file.getFile(),"zip","zip_"+ new Date().getTime() + ".zip" );
}
}

view raw

ZipHandler.java

hosted with ❤ by GitHub

The above code has the handler method, which has an @ObservesAsync and a @Zip annotation. This is a CDI annotation that configures this method to observe the FileEvent file, and the Qualifier to configure this observer to react only to events launched to the @Zip qualifier.

If you found this article interesting and want to explore design patterns, you can explore Java EE 8 Design Patterns and Best Practices. Following a practical approach, the book takes you through all the important concepts in a nuanced manner, so you can develop a thorough understanding of enterprise-specific design patterns and their implementation. The book is a complete guide to solving common design and architectural problems in your development environment.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *