Monday, January 2, 2023

Java for the enterprise: What to expect in Jakarta EE 10


Last year, Java EE completed its transfer to the Eclipse Foundation and adopted a new name, Jakarta EE. While this is a great achievement in and of itself, perhaps the most interesting part of that is that it’s now finally time to start looking forward.

As a quick recap, Table 1 shows key historic and future Jakarta EE dates, some of which are tentative. There are some changes from the version of the table I presented in an article in February 2020.

Oracle Java, Jakarta EE 10, Oracle Java Certification, Oracle Java Certification, Java Prep, Java Preparation, Oracle Java Tutorial and Materials

Table 1. The history and latest release projections for Java EE and Jakarta EE

Comparing the table shown in the previous article to this one, you can see that the JDK 11 compatibility theme moved from Jakarta EE 9 to Jakarta EE 9.1, which is still to be released this year.

While this obviously takes some time away from Jakarta EE 10, planning for that latter release has started to some degree nevertheless, and some of the individual specifications and API projects have started their discussions. Note that everything presented in this article is preliminary and represents the current state of what is thought to be the direction in which Jakarta EE 10 will be heading, but it provides no guarantees that any of this will actually end up in Jakarta EE 10.

It’s all about CDI alignment


One of the topics that is likely to be adopted for the Jakarta EE 10 overall theme might be “Contexts and Dependency Injection (CDI) alignment,” that is, closing the gap between Enterprise JavaBeans (EJB) and CDI. From roadmaps, to discussions among vendors, to wishes from the community, this often comes out on top.

Historically Jakarta EE has used different component models for many of its constituent specifications. Java Server Faces (JSF), now called Jakarta Server Faces, had its own managed beans as did, for example, the REST (JAX-RS), Java Servlet, and EJB specifications. For vendors this meant implementing similar things multiple times over, every time in a slightly different way, while for developers it meant learning similar things multiple times over—and especially wondering why certain things can’t be combined in their applications.

For instance, an interceptor can’t be applied to a Servlet method, while @RolesAllowed doesn’t work on either a Servlet method or a JSF-managed bean. To fix these issues, a single platform-wide component model was introduced in Java EE 6: CDI. The CDI API fully focuses on being a standalone component model with several well-defined services such as interceptors and decorators.

Jakarta Transactions (JTA) was one of the first APIs to start this alignment process by providing a CDI-compatible interceptor, @Transactional, and scope, @TransactionScope, in Java EE 7.

JSF followed right away by introducing new scopes such as @FlowScoped and a CDI version of the existing @ViewScoped in Java EE 7. Quite a few other things such as @Asynchronous, @Lock, @Startup/@DependsOn, and @Schedule were, unfortunately, not included as CDI versions in Java EE 7. Sadly, those didn’t even appear in Java EE 8, though that version did introduce Java EE Security (now Jakarta Security), which is built on top of CDI. That release also delivered JSF 2.3, which provided CDI-based injection and expression language lookup of a large number of artifacts. Additionally, JSF 2.3 officially deprecated its own managed bean system in favor of using CDI beans.

Jakarta EE is expected to pick up the pace again, providing CDI versions of those enterprise beans and common annotations, as well as upgrading and enhancing the existing CDI support in several Jakarta APIs.

Here are several changes you should expect in the Jakarta EE 10 specs.

Jakarta Server Faces


The next version of JSF will be JSF 4.0. Its own major theme will be removing legacy functionality that has already been deprecated. Plus, legacy features that haven’t been deprecated before will be deprecated and likely removed in a future release.

For example, the native expression language that JSF still includes will be removed. This was deprecated over 15 years ago but is still there. That expression language shows up in a number of API types, for example, here in ActionSource:

public interface ActionSource {
    MethodBinding getAction();
    void setAction(MethodBinding action);
    // other methods omitted for brevity
}

All methods referencing types from the native expression language, such as MethodBinding, will be removed.

Support for Jakarta Server Pages (JSP) as a view declaration language will be removed as well, meaning Facelets will remain as the only default view language. Corresponding with the potential overall Jakarta EE 10 theme, the native managed bean system will be completely removed, making CDI beans the designated bean type for JSF.

Finally, some of the extension tags will be removed, such as composite:extension. These were related to the big plans JSF designers once had for visual editors, such as those that existed for Microsoft Visual Basic. These plans never came to fruition, and despite some attempts, most of it was withdrawn. Some remnants of these plans, however, remained in JSF and will now finally be removed.

You can expect some new small features and refinements in the API, for instance, default methods in the PhaseListener interface, use of suppliers in several places, adding generics that were still not present, and small utility methods helpful for component libraries. One example: There will be a release() method on FacesContext as part of PrimeFaces.

As for bigger features, a prototype is currently in the works to add a simple REST lifecycle to JSF. This is not intended as a full-featured REST framework, but instead it is to simplify the use case where JSF applications now use a view action in combination with an empty page. This looks as follows:

@RequestScoped
public class RestBean {

   @Inject FacesContext context;
   
   @RestPath("/helloWorld")
   public String helloWorld() {
        return "Hello World! Postback is " + context.isPostBack();
   }
}

Another feature being looked at is supporting extensionless URLs by default or by using a single configuration option. JSF 2.3 provided basic support for this by officially supporting exact mapping, and JSF 4.0 may expand on this support. Thus, a URL such as http://localhost:8080/foo.xhtml (the current default) will be accessible via http://localhost:8080/foo as well.

Scopes have always played an important role in JSF, and one of the things the team is looking forward to is adding a new scope, @ClientWindowScoped, which builds on the Client Id feature that was introduced in JSF 2.2 as a base facility but was not expanded upon in JSF 2.3.

The Jakarta Security API


Jakarta Security was a new API in Java EE 8. It came out of the box with three authentication mechanisms: Basic, Form, and a variant on Form that’s best for working with JSF.

For the version in Jakarta EE 10, the plan is to add new authentication mechanisms. High on the list are at least Client-Cert and Digest, to make Jakarta Security a full replacement for authentication mechanisms provided by Java Servlet, and to add new methods to support OpenID, OAuth, and JSON Web Token (JWT). The latter is an especially interesting case, because during the Java EE transfer, JWT had already been added to MicroProfile. It’s an open question how to deal with this.

Supporting the CDI-alignment theme, the Jakarta Security wish list includes CDI-based alternatives for the common annotations @RolesAllowed and @RunAs, including, perhaps, supporting the existing annotations. Currently in Jakarta EE, @RolesAllowed is supported only by EJB, where it throws an exception if access is denied to a bean method. However, in MicroProfile or, more precisely in JWT, it’s implied that @RolesAllowed triggers a mandatory authentication mechanism invocation when access is initially denied to a Jakarta REST resource method. Jakarta Security should cover both cases and define those well.

A major new feature being considered for Jakarta Security is that of user-friendly authentication modules, thereby enabling custom authorization rules. There’s some history here. One of the main interfaces in Jakarta Security is the HttpAuthenticationMechanism, which is effectively an HTTP-specific and CDI-enabled ease-of-use layer on top of the lower-level ServerAuthModule from Jakarta Authentication.

By the way, there is a Jakarta Authorization feature that provides low-level portable authorization modules. However, due to the way modules must be created and installed, modules are not really suitable for use in ordinary applications. Let’s hope Jakarta Security provides a similar CDI-enabled ease-of-use layer.

A prototype for this functionality was developed all the way back in 2016, but it was not incorporated in Jakarta Security 1.0 due to lack of time to properly evaluate it. For instance, bridging role checking to an external service instead of assigning all roles when a caller is authenticated would look like the following:

@ApplicationScoped
public class MyAuthorizationModule {

    @Inject
    SecurityConstraints securityConstraints

    @Inject
    MyService service;
   
    @PostAuthenticate
    @PreAuthorize
    @ByRole
    public Boolean myLogic(
        Caller caller, Permission requestedPermission) {
        
        return securityConstraints.getRequiredRoles(requestedPermission)
                .stream()
                .anyMatch(role -> service.isInRole(caller, role));
    }
   
}

The authorization module is called by the container to check whether a caller can access a protected URL such as https://localhost:8080/myapp/admin/foo, or in response to HttpServletRequest.isCallerInRole(), or following a @RolesAllowed annotation.

As part of Jakarta Security, the lower-level Jakarta Authentication and Jakarta Authorization APIs may get some smaller updates as well. These APIs (technically service provider interfaces, or SPIs) are not directly aimed at application developers; the goal is to extend them somewhat and adding clarifications to help higher-levels APIs. For Jakarta Authorization, an important new feature planned is to allow low-level authorization modules to be installed per application—and allow that to be done by the application. Currently this can be done only at the server level.

The Jakarta Servlet API


Jakarta Servlet is the quintessential API in Jakarta EE. Over time it has been adapted to support the somewhat lesser known Jakarta Managed Beans 2.0 specification, meaning that in Jakarta EE, a servlet is a managed bean. In practice this means some CDI features are supported, such as @Inject, but for instance scopes or CDI-style interceptor bindings are not supported.

To align Jakarta Servlet further with CDI is difficult. More than most other APIs in Jakarta EE, Jakarta Servlet has a huge active user base that uses it separately from Jakarta EE, and there are several vendors that exclusively focus on this user base.

Oracle Java, Jakarta EE 10, Oracle Java Certification, Oracle Java Certification, Java Prep, Java Preparation, Oracle Java Tutorial and Materials
So far, the proposals for further alignment vary between multiple options. One is to include a Jakarta EE–specific chapter in the Jakarta Servlet specification that says that in a Jakarta EE environment, servlets should be full CDI beans. This would require no API changes, which is a plus, but it would still require the traditional Servlet base class to be extended, which by default delegates all HTTP methods to a single service() method. This, for instance, is not ideal for security interceptors.

A potential solution is to make all the methods from the Servlet base interface into default methods, so that in a Jakarta EE environment you could write something like the following:

@RequestScoped
@WebServlet("/foo/bar")
public class MyBean implements Servlet {
      
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        // ...
    }
}

Another proposal is to change nothing in the API but to specify that if a servlet is treated as a CDI bean, and the container detects (for example) that the service() method has not been overridden, the doGet() methods are called directly. Such a CDI bean would then almost look like a regular servlet:

@RequestScoped
@WebServlet("/foo/bar")
public class MyBean extends HttpServlet {
      
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        // ...
    }
}

Another CDI-alignment issue concerns the additional built-in beans for HttpServletRequest, HttpSession, and ServletContext, which are now defined by the CDI specification. Conceptually those don’t belong in the CDI spec, and for this reason alone it would be better if they were moved to the Jakarta EE part of the Jakarta Servlet spec. Practically, the injected HttpServletRequest is the most troublesome because it doesn’t define which HttpServletRequest is injected. ServerAuthModules and Filters can wrap it and after forwarding to another servlet, there’s another version of the request coming into view. Most implementations today inject HttpServletRequest in the state in which it entered the request pipeline, and this is often not what applications expect. A Jakarta Servlet native version of HttpServletRequest could provide the actual current request.

At the other end of the spectrum of alignment, there’s the issue in Jakarta EE that Jakarta REST, which listens to HTTP requests as well, technically does not depend on Jakarta Servlet. In a Jakarta EE environment, it always practically depends on Jakarta Servlet, but in other environments this doesn’t need to be the case. To align these two, the idea has been expressed to extract from Jakarta Servlet a low-level flexible HTTP API, on which both Jakarta Servlet and Jakarta REST could be based in Jakarta EE and, potentially, in other frameworks. In practice, this separation already takes place. For example, in GlassFish this is implemented by Grizzly, and in Tomcat there’s Coyote.

Besides these alignment issues, there are a number of more native features in the pipeline, with the most important one being RFC 6265: state-management cookies with SameSite behavior.

Other small enhancements to Jakarta Servlet include distinguishing between the query string and POST body parameters, as well as gaining an easier-to-use HttpServletRequestWrapper such that only a minimal amount of work has to be done to override the URL.

The Jakarta REST API


Like JSF, Jakarta REST has its own native managed bean system. Because Jakarta REST was introduced together with CDI in Java EE 6, it had some alignment facilities from the get-go, but nevertheless Jakarta REST uses its own injection annotations (specifically @Context) and its own rules around these.

Just like JSF 4.0, Jakarta REST 4.0 will drop its own managed bean system and its own injection annotations. This means that moving forward, Jakarta REST resources will be only CDI beans. An intermediate version, Jakarta REST 3.1, is planned, which will formally deprecate this managed bean system and will allow at least class-level injection of the artifacts currently injected using @Context via @Inject. This release will likely also deprecate the use of Java Architecture for XML Binding (JAXB) in the API, specifically by deprecating Link.JaxbLink and Link.JaxbAdapter.

In addition to the switch over to CDI, there will be a number of smaller features introduced. For instance, parameters annotated with @CookieParam, @FormParam, @HeaderParam, @MatrixParam, and @QueryParam can now also have an array type. In earlier versions of Jakarta EE, they could use only a type of List, Set, or SortedSet. For instance, now you can code the following:

@Path("/users")
public class UserResource {
    @GET
    public Response getUsers(@QueryParam("orderBy") String[] orderBy) {
        return …
    }
}

Another addition is a default exception mapper that implements ExceptionMapper<Throwable> and sets the response to status 500 unless the exception is a WebApplicationException. In that case, the mapper sends the embedded response and its own status code.

The Jakarta Concurrency API


The Java EE Concurrency API was first created in 2003, but it was then stalled for many years, only to be released in Java EE 7, seemingly under some time constraints.

The API shows its age a little by still strongly adhering to the container-managed principle. This practically means that the configuration of the concurrency resources is supposed to be done in a proprietary way using specific tools of the Jakarta EE server (for instance, an admin GUI, a CLI, or an XML file inside a server folder).

While this principle may have been the norm in 2003, the world moved on in the years that the Concurrency API lay dormant. More common, concurrency evolved to a hybrid model where resources can be defined and configured by either the server or the application. Therefore, a long overdue addition to the Jakarta Concurrency API is a @ManagedExecutorServiceDefinition—just like @LdapIdentityStoreDefinition and @DataSourceDefinition—which allows applications to define and configure their own managed executor.

By the way, the Jakarta Concurrency API is very important for the CDI-alignment story, because nearly all the things that are still very useful and available only in EJB are related to concurrency. This concerns specifically the following annotations:

◉ @Asynchronous
◉ @Lock and @AccessTimeout
◉ @Schedule and @Timeout
◉ @Stateless

@Asynchronous in EJB is pretty basic, so a newer version could go a little beyond those basics. One proposal is to optionally allow a managed thread pool to be specified on which the annotated method will be executed. That way with two such pools, you can avoid a certain type of deadlock for cooperating asynchronous methods. As with JWT for Jakarta Security, here too a potential difficulty is that MicroProfile has already introduced a CDI-based @Asynchronous (in the Fault Tolerance API, which is a little unexpected perhaps).

@Stateless itself will not be directly transferred into the Jakarta Concurrency API. One implied aspect is that @Stateless beans are pooled, and a single-bean instance is defined to handle only a single call at the same time. Together, these two beans would form a natural way to throttle concurrency. Discussions around this led to a proposed @Pooled or @MaxConcurrency annotation for the new version of the Jakarta Concurrency API.

A particular problem when doing concurrent programming in Jakarta EE is when, for example, an initial request thread holds a lot of contextual information, such as the current application for which the request is needed (for proper Java Naming and Directory Interface lookups), the authenticated identity, or the current active CDI scopes. When work starts in a new thread, some or all of that context needs to be transferred (propagated).

When the Jakarta Concurrency API was revived from initial work done in early 2000, the designers didn’t take CDI into account. This has been a major hindrance ever since because nothing concerning scopes propagates now in a portable way. To solve this problem, an explicit context propagation API is in the works. This API has been prototyped under MicroProfile, with a stated goal that it is to be included in the Jakarta Concurrency API.

Variants of CDI


With Jakarta EE likely having CDI alignment as one of its main themes, the main new feature that is being planned for CDI itself is another variant of CDI. The specification has already been split into three parts: Core CDI, CDI in Java SE, and CDI in Jakarta EE. The new variant, called CDI-Lite, will focus on build-time concerns, specifically detecting beans during build-time and providing a new kind of extension that can run during build-time.

There’s some interesting history here, because this is how EJB 1.0 actually worked; there was no reflection, and skeletons, stubs, and proxies were all generated using tools at build-time. Because this was seen as a lot of hassle, newer versions of EJB built those automatically at runtime using reflection, an approach later adopted by CDI. With CDI now explicitly supporting build-time, it’s gone full circle.

Plans for CDI-Lite are still greatly in flux, and it hasn’t even been decided yet whether CDI-Lite will be a proper subset of its higher layer, but potentially the stack could look approximately like the following:

1. Jakarta CDI: A small set of key annotations, shared with Guice, HK2, and Spring, including @Inject, @Named, @Qualifier, and @Scope
2. Jakarta CDI Lite: Beans, qualifiers (behavior), scopes (behavior), stereotypes, and build-time portable extensions
3. Jakarta CDI Core: Alternatives, decorators, runtime portable extensions (potentially, the build-time extensions are excluded)
4. Jakarta CDI EE: Rules for EJB beans and servlet components, bean names, and scope in expression language, specifically including JSF and JSP, built-in beans for Jakarta Transaction, Jakarta Security, and Jakarta Servlet

While most focus has been on CDI-Lite until now, some features for the main CDI functionality are planned as well. Many of those are specifically for the overall CDI-alignment theme, meaning that they are intended to make it easier for other APIs in Jakarta EE to integrate with CDI.

One such proposal concerns the introduction of executable methods, which effectively lets arbitrary business methods in beans use parameter injection. (Note that CDI already supports this for some callback methods.) An example would be the following:

@RequestScoped
public class MyBean {
      String hello(@ConfigOption("foo") String foo) {
   }
}

A framework such as Jakarta REST or JSF, but of course also application code itself, could then execute this method in some way. Perhaps something like:

beanManager.execute(bean, method);

Some APIs building on CDI struggle because they have fewer options to define or use certain things than CDI itself has, making them a second class citizens. Two examples concern bean-defining annotations and built-in beans.

At the moment, only CDI itself defines which annotations are bean defining. To truly integrate other APIs, they should also be able to create bean-defining annotations. This is something the next version of CDI will likely take a look at.

As discussed above, the CDI spec defines several built-in beans, and so do APIs such as Jakarta Security, JSF and, soon, Jakarta REST. The way this is typically done is via a CDI extension, which programmatically adds a Bean<T> instance. These are low-level types, so they have to find their own decorators and generate a proxy to apply them.

Unfortunately, there’s no portable API in CDI to find decorators and generate proxies, so many implementations of Jakarta APIs don’t actually do this. The result is that such built-in beans are not decoratable and also can’t be specialized, which can be quite problematic.

Built-in beans might also need the ability to obtain the current InjectionPoint. There’s currently no well-defined portable way to obtain such an InjectionPoint from within a Bean<T> instance. Making this possible is proposed for the next version of CDI.

Another proposed feature gives interceptors in CDI access to their actual (nonbinding) annotation parameters. Currently there’s no portable way to achieve this, so interceptors resort to looking at their target class and inspecting that. This works for interceptor annotations that are physically present on those classes, but it does not work for interceptors that have been dynamically added.

There are a few other CDI proposals that have been discussed less but are nevertheless worth mentioning.

The first is the ability to easily apply interceptors to built-in beans. Interceptors are easy to apply to your own code, but they are more troublesome to add to existing types. Using a producer that’s an @Alternative can use the InterceptionFactory, but then you need to get ahold of the type that the @Alternative overrides. This can be done using BeanManager#getBeans and some filtering, but it’s quite verbose. It would be much easier if this overridden instance (the instance that would have been selected for a type if you didn’t provide your alternative producer) could be injected directly.

The second issue concerns the programmatic API for obtaining bean instances. This API should provide the same expressive power to select instances that the declarative (injection) API has. At the moment, this is not the case for beans where the beans’ producer or Bean<T> makes use of an InjectionPoint. As a contrived example, consider the MicroProfile Config API, where a combination of the ConfigProperty qualifier and the name of the injected field is used to obtain the right configuration value. Via injection, this works as follows:

@Inject
@ConfigProperty
String foo;

The inputs to the selection mechanism here are string, ConfigProperty, and foo. The last part is something that can’t be provided to the programmatic selection mechanism today. In a proposed feature for CDI, this would be possible and would look something like the following:

CDI.current()
      .select(
           String.class, 
           new ConfigProperty.Literal(),
           injectionPoint().withMemberName("foo"))
       .get();

Other Jakarta EE 10 APIs


Several other Jakarta EE APIs have pending new features that have not been actively discussed as candidates for inclusion in Jakarta EE 10.

For example, Jakarta Persistence has ideas around adding support for transforming Java Persistence Query Language queries to the Criteria API and the other way around, adding higher-level pagination support (the well-known filtering, sorting, and paging paradigm), adding support for specifying which data a fetch graph should not fetch (as opposed to specifying what it should fetch), and supporting some smaller things such as allowing empty collections as a parameter in an in(…) clause.

Likewise, Jakarta Messaging has a lot of pending new features. During the Java EE 8 cycle, a number of them had actually been worked on quite a bit for what was to become Messaging 2.1 (which was never released). Specifically features for the CDI-alignment story have been proposed, such as CDI Message Consumers (having a CDI bean listen to incoming messages) and replacing the string-based activationConfig, which is in practice a rather thin layer on top of the original XML format used to configure message-driven beans. Smaller features include being able to easily send JSON- or XML-based messages.

There are also various new APIs in the works, for instance, NoSQL and model-view-controller, which may target Jakarta EE 10. For years now, there has also been talk about including a caching and a configuration API in Jakarta EE. A configuration API actually came to fruition but was developed in MicroProfile after an attempt for Jakarta EE was aborted during the Java EE 8 cycle.

Development of a temporary caching API started as early as 2001, in JSR 107: JCACHE. This was a candidate to include in Jakarta EE multiple times, but it never happened. Whether JCACHE will be transferred to Eclipse and finally be included in Jakarta EE 10 is a big question, and at this point, I don’t know the answer.

Source: oracle.com

Friday, December 30, 2022

JavaFX - Overview

JavaFX - Overview, Oracle Java Exam, Oracle Java Prep, Oracle Java Tutorial and Materials, Oracle Java Career, Java Skills, Java Job

Rich Internet Applications are those web applications which provide similar features and experience as that of desktop applications. They offer a better visual experience when compared to the normal web applications to the users. These applications are delivered as browser plug-ins or as a virtual machine and are used to transform traditional static applications into more enhanced, fluid, animated and engaging applications.

Unlike traditional desktop applications, RIA’s don’t require to have any additional software to run. As an alternative, you should install software such as ActiveX, Java, Flash, depending on the Application.

In an RIA, the graphical presentation is handled on the client side, as it has a plugin that provides support for rich graphics. In a nutshell, data manipulation in an RIA is carried out on the server side, while related object manipulation is carried out on the client side.

We have three main technologies using which we can develop an RIA. These include the following −

◉ Adobe Flash
◉ Microsoft Silverlight
◉ JavaFX

Adobe Flash

This software platform is developed by Adobe Systems and is used in creating Rich Internet Applications. Along with these, you can also build other Applications such as Vector, Animation, Browser Games, Desktop Applications, Mobile Applications and Games, etc.

This is the most commonly used platform for developing and executing RIA’s with a desktop browser penetration rate of 96%.

Microsoft Silverlight

Just like Adobe flash, Microsoft Silverlight is also a software application framework for developing as well as executing Rich Internet Applications. Initially this framework was used for streaming media. The present versions support multimedia, graphics, and animation as well.

This platform is rarely used with a desktop browser penetration rate of 66%.

JavaFX

JavaFX is a Java library using which you can develop Rich Internet Applications. By using Java technology, these applications have a browser penetration rate of 76%.

What is JavaFX?


JavaFX is a Java library used to build Rich Internet Applications. The applications written using this library can run consistently across multiple platforms. The applications developed using JavaFX can run on various devices such as Desktop Computers, Mobile Phones, TVs, Tablets, etc.

To develop GUI Applications using Java programming language, the programmers rely on libraries such as Advanced Windowing Toolkit and Swing. After the advent of JavaFX, these Java programmers can now develop GUI applications effectively with rich content.

Need for JavaFX


To develop Client Side Applications with rich features, the programmers used to depend on various libraries to add features such as Media, UI controls, Web, 2D and 3D, etc. JavaFX includes all these features in a single library. In addition to these, the developers can also access the existing features of a Java library such as Swing.

JavaFX provides a rich set of graphics and media API’s and it leverages the modern Graphical Processing Unit through hardware accelerated graphics. JavaFX also provides interfaces using which developers can combine graphics animation and UI control.

One can use JavaFX with JVM based technologies such as Java, Groovy and JRuby. If developers opt for JavaFX, there is no need to learn additional technologies, as prior knowledge of any of the above-mentioned technologies will be good enough to develop RIA’s using JavaFX.

Features of JavaFX


Following are some of the important features of JavaFX −

◉ Written in Java − The JavaFX library is written in Java and is available for the languages that can be executed on a JVM, which include − Java, Groovy and JRuby. These JavaFX applications are also platform independent.

◉ FXML − JavaFX features a language known as FXML, which is a HTML like declarative markup language. The sole purpose of this language is to define a user Interface.

◉ Scene Builder − JavaFX provides an application named Scene Builder. On integrating this application in IDE’s such as Eclipse and NetBeans, the users can access a drag and drop design interface, which is used to develop FXML applications (just like Swing Drag & Drop and DreamWeaver Applications).

◉ Swing Interoperability − In a JavaFX application, you can embed Swing content using the Swing Node class. Similarly, you can update the existing Swing applications with JavaFX features like embedded web content and rich graphics media.

◉ Built-in UI controls − JavaFX library caters UI controls using which we can develop a full-featured application.

◉ CSS like Styling − JavaFX provides a CSS like styling. By using this, you can improve the design of your application with a simple knowledge of CSS.

◉ Canvas and Printing API − JavaFX provides Canvas, an immediate mode style of rendering API. Within the package javafx.scene.canvas it holds a set of classes for canvas, using which we can draw directly within an area of the JavaFX scene. JavaFX also provides classes for Printing purposes in the package javafx.print.

◉ Rich set of API’s − JavaFX library provides a rich set of API’s to develop GUI applications, 2D and 3D graphics, etc. This set of API’s also includes capabilities of Java platform. Therefore, using this API, you can access the features of Java languages such as Generics, Annotations, Multithreading, and Lambda Expressions. The traditional Java Collections library was enhanced and concepts like observable lists and maps were included in it. Using these, the users can observe the changes in the data models.

◉ Integrated Graphics library − JavaFX provides classes for 2d and 3d graphics.

◉ Graphics pipeline − JavaFX supports graphics based on the Hardware-accelerated graphics pipeline known as Prism. When used with a supported Graphic Card or GPU it offers smooth graphics. In case the system does not support graphic card then prism defaults to the software rendering stack.

History of JavaFX


JavaFX was originally developed by Chris Oliver, when he was working for a company named See Beyond Technology Corporation, which was later acquired by Sun Microsystems in the year 2005.

The following points give us more information of this project −

◉ Initially this project was named as F3 (Form Follows Functions) and it was developed with an intention to provide richer interfaces for developing GUI Applications.

◉ Sun Microsystems acquired the See Beyond company in June 2005, it adapted the F3 project as JavaFX.

◉ In the year 2007, JavaFX was announced officially at Java One, a world wide web conference which is held yearly.

◉ In the year 2008, Net Beans integrated with JavaFX was available. In the same year, the Java Standard Development Kit for JavaFX 1.0 was released.

◉ In the year 2009, Oracle Corporation acquired Sun Microsystems and in the same year the next version of JavaFX (1.2) was released as well.

◉ In the year 2010, JavaFX 1.3 came out and in the year 2011 JavaFX 2.0 was released.

◉ The latest version, JavaFX8, was released as an integral part of Java on 18th of March 2014.

Source: tutorialspoint.com

Wednesday, December 28, 2022

How to build applications with the WebSocket API for Java EE and Jakarta EE

Oracle Java Certification, Java Prep, Java Certification Exam, Java Tutorial and Materials, Java Career, Java Skills, Java Jobs



WebSocket is a two-way communication protocol that lets clients send and receive messages over a single connection to a server endpoint. The Jakarta WebSocket API, part of the Jakarta EE platform, can be used to develop WebSocket server endpoints as well as WebSocket clients. This article provides a brief overview of the Jakarta WebSocket specification, and I’ll show how to construct an application using WebSockets.

I’ll cover the Jakarta WebSocket API as it stands as part of the Jakarta EE 9 platform release. That said, the examples in this article will work with Jakarta EE 8 or Java EE 7 or Java EE 8 applications. The main difference is that the namespace for Jakarta EE 9 is jakarta.*; in earlier releases, it was javax.*. Therefore, if you are using a previous release, change the namespace to javax.*.

WebSocket is a vendor-independent standard. If you’re curious about the WebSocket protocol, it’s covered in depth in IETF RFC 6455. Many tutorials are published online. You can also read the documentation for the WebSocket interface in JDK 15.

To communicate with WebSocket, you must configure a server endpoint. The simplest endpoint is a standard Java class that either is annotated with @ServerEndpoint or extends the jakarta.websocket.Endpoint abstract class.

An endpoint also contains a method that’s annotated with @OnMessage. The @ServerEndpoint annotation accepts the URI at which the WebSocket server will accept messages that need to be sent. The URI can also be used to register clients as recipients for WebSocket messages.

The following simple endpoint accepts a string-based message at the endpoint URI /basicEndpoint and performs an activity with that message once it has been received. A client can connect to the server endpoint URI to open the connection, which will remain open for sending and receiving messages for the duration of the session.

@ServerEndpoint(value = "/basicEndpoint")
public class BasicEndpoint { 
    @OnMessage
    public void onMessage(Session session,
                                  String message){
        // perform an action
    }
}

In the following sections, you’ll see the wide variety of options available for developing more-sophisticated WebSocket solutions. However, the overall concept for generating a WebSocket endpoint remains very much the same as the previous example.

Digging into the specification


You can develop WebSocket endpoints using either an annotation-based or programmatic approach. You can use the @ServerEndpoint annotation to specify that a class is used as a WebSocket server endpoint. The alternative to using @ServerEndpoint is to extend the jakarta.websocket.Endpoint abstract class. The examples for this article use the annotation approach. Similarly, you can use the @ClientEndpoint annotation to specify that a standard Java class is used to accept WebSocket messages. @ServerEndpoint and @ClientEndpoint can specify the following attributes:

◉ value: Specifies a URI path at which the server endpoint will be deployed.
◉ decoders: Specifies a list of classes that can be used to decode incoming messages to the WebSocket endpoint. Classes implement the Decoder interface.
◉ encoders: Specifies a list of classes that can be used to encode outgoing messages from the WebSocket endpoint. Classes implement the Encoder interface.
◉ subprotocols: Specifies a string-based list of supported subprotocols.
◉ configurator: Lists a custom implementation of ServerEndpointConfiguration.Configurator.

The specification defines a number of annotations that can be placed on method declarations of a WebSocket endpoint class. Each of the annotations can be used only once per class, and they are used to decorate methods which contain implementations that are to be invoked when the corresponding WebSocket events occur. The method annotations are as follows:

◉ @OnOpen: When it is specified on a method, it will be invoked when a WebSocket connection is established. The method can optionally specify Session as the first parameter and EndpointConfig as a second parameter.
◉ @OnMessage: When it is specified on a method, it will be invoked when a message is received. The method can optionally specify Session as the first parameter and String (message) as a second parameter.
◉ @OnClose: When it is specified on a method, it will be invoked when a WebSocket connection is closed. The method can optionally specify Session as the first parameter and CloseReason as a second parameter.
◉ @OnError: When it is specified on a method, it will be invoked when an Exception is being thrown by any method annotated with @OnOpen, @OnMessage, or @OnClose. The method can optionally specify Session as the first parameter along with Throwable parameters.

Configuring a WebSocket project


To get started with Jakarta WebSocket, you must either add the websocket-api dependency to a project or add the jakarta-ee dependency to make use of the entire platform. Both the Jakarta EE full profile and the web profile contain the Jakarta WebSocket dependency.

<dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee}</version>
</dependency>

For projects that will contain an @ClientEndpoint, you must add an implementation as a dependency. In this case, I add the Tyrus client implementation by adding the following dependency. (Project Tyrus, from Oracle, is a JSR 356 Java API for WebSocket reference implementation.)

<dependency>
            <groupId>org.glassfish.tyrus.bundles</groupId>
            <artifactId>tyrus-standalone-client</artifactId>
            <version>2.0.0-M3</version>
 </dependency>

Creating a chat application using WebSocket


Here’s an application that uses WebSocket server endpoints with a JavaScript WebSocket client to send and receive messages. This particular example, called AcmeChat, uses Maven, but another build system such as Gradle would work just as well. This example will be deployed to Payara 5.202 running on Jakarta EE 9.

To follow along, you can clone the source code from GitHub.

The WebSocket endpoint. To begin, create a Maven web application and add the Jakarta EE 9 API dependency, along with any others that may be used, as shown in Listing 1. In this situation, you could also use the Jakarta EE Web Profile to make the application lighter.

Listing 1. Adding the Jakarta EE 9 API dependency

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.employeeevent</groupId>
    <artifactId>AcmeChat</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>AcmeChat-1.0-SNAPSHOT</name>
    
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
        <jakartaee>9.0.0-RC3</jakartaee>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee}</version>
        </dependency>
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>8.0</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.tyrus.bundles</groupId>
            <artifactId>tyrus-standalone-client</artifactId>
            <version>2.0.0-M3</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>jakarta.platform</groupId>
                                    <artifactId>jakarta.jakartaee-api</artifactId>
                                    <version>${jakartaee}</version>
                                    <type>pom</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Oracle Java Certification, Java Prep, Java Certification Exam, Java Tutorial and Materials, Java Career, Java Skills, Java Jobs
Next, create the WebSocket server endpoint class named com.employeeevent.acmechat.ChatEndpoint. The source code for this class is shown in Listing 2. Annotate the class with @ServerEndpoint and specify a URI path of "/chatEndpoint/{username}" for the value attribute. Note the path parameter that is enclosed in curly braces at the end of the URI. This allows the endpoint to accept a parameter. In this case, I will be sending a message that’s composed of a Java object. Therefore, I need to use an encoder and decoder to translate the message from the client to the server. I can specify an encoder and decoder via attributes of @ServerEndpoint.

Listing 2. Creating the WebSocket server endpoint class

package com.employeeevent.acmechat;

import jakarta.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import jakarta.websocket.EncodeException;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/chatEndpoint/{username}",
        encoders = {MessageEncoder.class},
        decoders = {MessageDecoder.class})
public class ChatEndpoint {
    
    @Inject
    ChatSessionController chatSessionController;

    private static Session session;
    private static Set<Session> chatters = new CopyOnWriteArraySet<>();

    @OnOpen
    public void messageOpen(Session session,
            @PathParam("username") String username) throws IOException,
            EncodeException {
        this.session = session;
        Map<String,String> chatusers = chatSessionController.getUsers();
        chatusers.put(session.getId(), username);
        chatSessionController.setUsers(chatusers);
        chatters.add(session);
        Message message = new Message();
        message.setUsername(username);
        message.setMessage("Welcome " + username);
        broadcast(message);
    }

    @OnMessage
    public void messageReceiver(Session session,
            Message message) throws IOException, EncodeException {
        Map<String,String> chatusers = chatSessionController.getUsers();
        message.setUsername(chatusers.get(session.getId()));
        broadcast(message);
    }

    @OnClose
    public void close(Session session) {
        chatters.remove(session);
        Message message = new Message();
        Map<String,String> chatusers = chatSessionController.getUsers();
        String chatuser = chatusers.get(session.getId());
        message.setUsername(chatuser);
        chatusers.remove(chatuser);
        message.setMessage("Disconnected from server");

    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.out.println("There has been an error with session " + session.getId());
    }

    private static void broadcast(Message message)
            throws IOException, EncodeException {
       
        chatters.forEach(session -> {
            synchronized (session) {
                try {
                    session.getBasicRemote().
                            sendObject(message);
                } catch (IOException | EncodeException e) {
                    e.printStackTrace();
                }
            }
        });
    }

}

Then, the endpoint class declares a field, identified as session, that’s used to hold the WebSocket Session and another Set<Session>, identified as chatters, to hold each of the connected chat user sessions. The class also injects an @ApplicationScoped controller class entitled ChatSessionController for storing users in a simple HashMap, which is shown in Listing 3.

Listing 3. Endpoint class declaring fields to hold the WebSocket session and chat user sessions

@Named
@ApplicationScoped
public class ChatSessionController implements java.io.Serializable {
    
    private Map<String, String> users = null;
    
    public ChatSessionController(){}
    
    @PostConstruct
    public void init(){
         users = new HashMap<>();
    }

    /**
     * @return the users
     */
    public Map<String, String> getUsers() {
        return users;
    }

    /**
     * @param for the users
     */
    public void setUsers(Map<String, String> users) {
        this.users = users;
    }
    
}

The ChatEndpoint class declares four methods for handling the WebSocket server events and a method named broadcast() that’s used to broadcast messages to each of the connected clients, all of which are described below:

private static void broadcast(Message message)
            throws IOException, EncodeException {
       
        chatters.forEach(session -> {
            synchronized (session) {
                try {
                    session.getBasicRemote().
                            sendObject(message);
                } catch (IOException | EncodeException e) {
                    e.printStackTrace();
                }
            }
        });
}

◉ The broadcast() method is private and static, and it accepts a Message object. The method simply traverses the set of chat sessions, stored within the chatters field, and within a synchronized block calls upon the getBasicRemote().sendObject() method for each session, sending the Message object.

◉ The messageOpen() method, annotated with @OnOpen, is executed when the connection is opened. The method accepts a Session and an @PathParam string, which accepts the username substitute variable that’s contained within the @ServerEndpoint value attribute. Next, the Session and username are both stored, and a Message object is constructed using the username and message text, and finally the message is broadcast via the invocation of the broadcast() method.

◉ The messageReceiver() method, annotated with @OnMessage, is executed when the WebSocket message is received. The method accepts a Session and Message; it uses the ChatSessionController to obtain the username of the user associated with the session and stores it in the Message object. The message is then broadcast by passing the Message to the broadcast() method.

◉ The close() method, annotated with @OnClose, is invoked when the connection is closed. This method accepts a Session, which is then removed from the Set of chatters, as well as the chatusers Map. The session is then used to obtain the corresponding username from the ChatSessionController, and it is stored in a new Message object, which is subsequently broadcast to alert the other chatters that the user has disconnected.

◉ The onError() method, annotated with @OnError, is invoked whenever one of the other annotated methods throws an exception. This WebSocket endpoint can accept messages from any WebSocket client, as long as the client has an active session with the endpoint. To communicate with the endpoint, the client will connect to the following URI: ws://<hostname>:<port>/AcmeChat/chatEndpoint.

The WebSocket client. You can write a client in a variety of languages and still have the ability to communicate with the WebSocket endpoint. In this example, I wrote the client in JavaScript and invoked it via a Jakarta Server Faces front end.

Look at Listing 4, which contains the source code for the client. Note that the body of the client is written in Jakarta Server Faces and uses PrimeFaces components for the user interface. The user interface contains an inputText field for the username, an inputTextarea for the message, and two commandButton widgets.

One of the commandButton widgets invokes a JavaScript function named chatRelay(), which opens a connection to the WebSocket. The other button invokes a JavaScript function named send() to send the message from the inputTextarea to the WebSocket endpoint.

Listing 4. Source code for the client

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <script type="text/javascript">
                var ws;
                function chatRelay()
                {
                    var username = document.getElementById("chatForm:username").valueOf();

                    if ("WebSocket" in window)
                    {
                        var json = {
                            'username': username,
                            'message': ""
                        };

                        // Open WebSocket
                        ws = new WebSocket("ws://localhost:8080/AcmeChat/chatEndpoint/" + username.value);
                        ws.onopen = function ()
                        {
                            // Perform handling when connection is opened
                        };
                        ws.onmessage = function (evt)
                        {
                            var json = JSON.parse(evt.data);
                            var currentValue = document.getElementById('output').innerHTML;
                            document.getElementById('output').innerHTML =
                                    currentValue +
                                    '<br />' +
                                    json.username + ": " + json.message;

                        };

                        ws.onclose = function ()
                        {
                            // websocket is closed.
                            alert("Connection is closed...");
                        };

                    } else
                    {
                        // The browser doesn't support WebSocket
                        alert("WebSocket NOT supported by your Browser!");
                    }
                }

            function send() {
                var username = document.getElementById('chatForm:username').valueOf();
                var message = document.getElementById('chatForm:chatText').valueOf();
                var json = {
                    'username': username.value,
                    'message': message.value
                };
                ws.send(JSON.stringify(json));
                return false;
            }
        </script>
    </h:head>
    <h:body>
        <h:form id="chatForm">
            <h:outputLabel for="username" value="Username: "/>
            <p:inputText id="username" />
            <br/>
            <p:commandButton id="wsRelay" type="button" value="Connect"
                             onclick="chatRelay();" update="chatText,sendMessage"/>
            <br/><br/>

            <p:inputTextarea id="chatText" cols="30" rows="10" style="visibility: #{chatSessionController.users ne null? 'visible':'hidden'}"></p:inputTextarea>
            <br/><br/>

            <p:commandButton id="sendMessage" type="button" value="Send"
                             style="visibility: #{chatSessionController.users ne null? 'visible':'hidden'}"
                             onclick="send();"/>
        </h:form>
        <br/><br/>
        <div id="output"></div>
    </h:body>

</html>

To open a connection to the endpoint, the chatRelay() function accepts the username from the client. Next, it checks to ensure that the client’s browser will work with WebSockets and, if it won’t, a message is presented on the client. If the browser is compatible with WebSockets, a new JSON object is created, passing the username and message text. The WebSocket is then opened by passing the URI to the WebSocket endpoint and appending the username to be passed in as a path parameter, for example:

ws = new WebSocket("ws://localhost:8080/AcmeChat/chatEndpoint/" + username.value);

At this point, the WebSocket client is listening for responses from the server, and there are callback functions that await the server responses. The ws.onopen function, shown below, is invoked when the connection is opened, invoking any handling code that may be present:

ws.onopen = function ()
        {
  // Perform handling
        };

The ws.onmessage function, shown below, accepts an event parameter. The event is the message that has been received from the server endpoint. In this case, I used the JavaScript JSON API to parse the data and populate the chat screen with the incoming message text.

ws.onmessage = function (evt)
        {
            var json = JSON.parse(evt.data);
            var currentValue = document.getElementById('output').innerHTML;
            document.getElementById('output').innerHTML =
                    currentValue +
                    '<br />' +
                    json.username + ": " + json.message;

        };

The ws.onclose function, shown below, is invoked when the WebSocket server connection is disconnected, performing any processing code, as required. An example would be a case where the network connection was lost or the WebSocket endpoint was shut down. In such a case, the client could be alerted that the connection was closed.

ws.onclose = function ()
        {
            // websocket is closed.
            alert("Connection is closed...");
        };

Once the client session has been started and the WebSocket client is listening, any messages received from the WebSocket endpoint will be published via the ws.onmessage handler. The JavaScript send() function, shown below, is then used to send any messages that the user types into the inputTextarea to the server endpoint for broadcasting to any listening clients. The send() function creates a JSON object from the client username and message and sends it to the endpoint using the ws.send function, along with a little help from the JSON.stringify utility to help parse the JSON.

function send() {
    var username = document.getElementById('chatForm:username').valueOf();
    var message = document.getElementById('chatForm:chatText').valueOf();
    var json = {
        'username': username.value,
        'message': message.value
    };
    ws.send(JSON.stringify(json));
    return false;
}

Using this client configuration, two or more different clients can connect to the same WebSocket endpoint and communicate with each other in chat-room style.

The decoder and encoder. When the JavaScript client sends a message to the endpoint, it is in JSON format. The WebSocket endpoint accepts a plain old Java object named Message, which contains the username and message. The decoder and encoder classes transform the client-side messages to the server-side message object, and vice versa. The Jakarta WebSocket API makes it easy to develop decoders and encoders by simply implementing the Decoder or Encoder interfaces, respectively.

Listing 5 shows the Decoder class implementation, which is named MessageDecoder. This class decodes the client-side message into a Message object for processing by the WebSocket server. The interface uses generics to implement the decoder for the accepted Java object. The class overrides four methods: init(), willDecode(), decode(), and destroy().

Much like the WebSocket endpoint, the decoder is very much event-based. The init() method accepts an EndpointConfig object, and it is invoked when the message is sent from the client to the endpoint. The willDecode() method, which accepts a string-based message, is invoked next to return a boolean indicating whether the incoming message is in the correct format. If the message is in the correct format, the decode() method is invoked, again accepting a string-based message in JSON format, and the message is decoded into the Message object for processing via the endpoint. Lastly, the destroy() method is invoked when the client session becomes invalid.

Listing 5. The Decoder class implementation

package com.employeeevent.acmechat;

import java.io.StringReader;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.websocket.DecodeException;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;

public class MessageDecoder implements Decoder.Text<Message> {

  @Override
  public Message decode(String jsonMessage) throws DecodeException {

    JsonObject jsonObject = Json
        .createReader(new StringReader(jsonMessage)).readObject();
    Message message = new Message();
    message.setUsername(jsonObject.getString("username"));
    message.setMessage(jsonObject.getString("message"));
    return message;

  }

  @Override
  public boolean willDecode(String jsonMessage) {
    try {
      // Check if incoming message is valid JSON
      Json.createReader(new StringReader(jsonMessage)).readObject();
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  @Override
  public void init(EndpointConfig ec) {
    System.out.println("Initializing message decoder");
  }

  @Override
  public void destroy() {
    System.out.println("Destroyed message decoder");
  }

}

Listing 6 shows the Encoder class implementation, which is named MessageEncoder. This class encodes the server-side Message object to a JsonObject to be passed back to the client for processing. The interface uses generics to implement the encoder for the accepted Java object.

The class then overrides three methods: init(), encode(), and destroy(). Again, much like the WebSocket endpoint, the encoder is very much event-based in that the init() method accepts an EndpointConfig object, and it’s initiated once for each client session that is opened. The encode() method accepts the object being encoded, in this case Message, and performs processing to translate that object into JSON before it’s sent back to the client. Lastly, the destroy() method is invoked when the client session becomes invalid.

Listing 6. The Encoder class implementation

package com.employeeevent.acmechat;

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.websocket.EncodeException;
import jakarta.websocket.Encoder;
import jakarta.websocket.EndpointConfig;

public class MessageEncoder implements Encoder.Text<Message> {

  @Override
  public String encode(Message message) throws EncodeException {

    JsonObject jsonObject = Json.createObjectBuilder()
        .add("username", message.getUsername())
        .add("message", message.getMessage()).build();
    return jsonObject.toString();

  }

  @Override
  public void init(EndpointConfig ec) {
    System.out.println("Initializing message encoder");
  }

    @Override
    public void destroy() {
        System.out.println("Destroying encoder...");
    }
    
}

The client endpoint


You can develop a client endpoint to communicate with a WebSocket server endpoint. The simplest client endpoint is a standard Java class that is annotated with @ClientEndpoint. You can see the full source code of a ClientEndpoint example in Listing 7.

Listing 7. Code for a client endpoint

@ClientEndpoint
public class BasicClient {
    
    Session session = null;
    private MessageHandler handler;
    
    public BasicClient(URI endpointURI) {
        try {
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, endpointURI);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        try {
        session.getBasicRemote().sendText("Opening connection");
        } catch (IOException ex){
            System.out.println(ex);
        }
    }
    
    public void addMessageHandler(MessageHandler msgHandler) {
        this.handler = msgHandler;
    }
    
    @OnMessage
    public void processMessage(String message) {
        System.out.println("Received message in client: " + message);
    }
    
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException ex) {
            Logger.getLogger(BasicClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    
     public static interface MessageHandler {

        public void handleMessage(String message);
    }

In this example, the ClientEndpoint is named BasicClient. A Session and MessageHandler are declared within the class, and the constructor accepts a URI. Upon instantiation via the constructor, a ContainerProvider.getWebsocketContainer() is called to obtain a WebsocketContainer instance identified as container. The container.connectToServer() method is then invoked, passing the endpoint URI to instantiate the client connection.

The client contains a method named onOpen(), which is annotated with @OnOpen, and accepts a Session. This method is invoked when the ClientEndpoint connection is open, and it sets the session and then calls upon the getBasicRemote().sendText() method to send a message to the client to indicate the connection is open.

The client also contains a method named processMessage(), annotated with @OnMessage, which accepts a string. This method is called upon when a message is received from the ServerEndpoint. The client sendMessage() method also accepts a string, and it calls upon the session.getBasicRemote().sendText() method to send the message to the ServerEndpoint.

This particular example also contains an internal MessageHandler interface and an addMessageHandler() method, which are used to send the messages from the client. You can use the following code to work with the client:

// open websocket
final BasicClient clientEndPoint = new BasicClient(
        new URI("ws://localhost:8080/AcmeChat/basicEndpoint"));
// add listener
clientEndPoint.addMessageHandler(new BasicClient.MessageHandler() {
    public void handleMessage(String message) {
        System.out.println(message);
    }
});
// send message to websocket
clientEndPoint.sendMessage("Message sent from client!");

WebSocket customization


Sometimes you have a requirement to develop custom implementations, such as client/server handshake policies or state processing. For such cases, the ServerEndpointConfig.Configurator provides an option allowing you to create your own implementation. You can implement the following methods to provide customized configurations:

◉ getNegotiatedSubProtocol(List<String> supported, List<String> requested): Allows a customized algorithm to determine the selection of the subprotocol that’s used
◉ getNegotiatedExtensions(List<Extension> installed, List<Extension> requested): Allows a customized algorithm to determine the selection of the extensions that are used
◉ checkOrigin(String originHeaderValue): Allows the specification of an origin-checking algorithm
◉ modifyHandshake(ServerEndpointConfig sec, HandshakeRequest req, HandshakeResponse res): Allows for modification of the handshake response that’s sent back to the client
◉ getEndpointInstance(Class<T> endpointClass): Allows a customized implementation for the creation of an Endpoint instance

The same holds true for the ClientEndpoint.Configurator, in that the configurator allows for customization of some algorithms during the connection initialization phase. You can customize the configuration using these two methods:

◉ beforeRequest(Map<String, List<String>> headers): Allows for the modification of headers before a request is sent
◉ afterResponse(HandshakeResponse res): Allows for the customization of the processing for a handshake response

Source: oracle.com