© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
D. R. HeffelfingerPayara Micro Revealedhttps://doi.org/10.1007/978-1-4842-8161-1_12

12. Payara Micro Specific Features

David R. Heffelfinger1  
(1)
Fairfax, VA, USA
 

In previous chapters, we’ve been focusing primarily on Payara Micro’s implementation of the MicroProfile standard. The benefit of coding against a standard is that we are not tied to a specific implementation, as code written against the standard can be deployed to any implementation.

In this chapter, we will cover some Payara Micro specific features that, while very useful, are specific to Payara Micro and not portable across implementations.

Automatic Clustering

Clustering is done automatically in Payara Micro; when two instances of Payara Micro are started on the same network, they automatically cluster together, with absolutely no configuration needed.

If we don’t want Payara Micro instances to join a cluster, we can start Payara Micro with the --noCluster command-line argument

Payara Micro (and Payara Server, for that matter) has an in-memory data grid used to share data across instances in a cluster. Payara’s data grid is based on the popular open source Hazelcast in-memory data grid. For the most part, the data grid is transparent to us as application developers deploying applications to Payara Micro; however, it is used behind the scenes to implement Payara Micro specific clustering features, such as having application scoped CDI beans be shared across a cluster and allowing to fire CDI events to observer methods running on another instance of Payara Micro on the same cluster.

Clustered Application Scoped CDI Beans

With Payara Micro, we can share the same instance of an application scoped bean across nodes in a network. To do so, all we have to do is annotate the application scoped bean with the @Clustered annotation, as illustrated in the following example.
package com.ensode.clusteredcdiappscopedbeans;
import fish.payara.cluster.Clustered;
//additional imports omitted
@Clustered
@ApplicationScoped
//Clustered application scoped bean must be serializable
public class DataCache implements Serializable {
  private Map<String, String> cachedValueMap = new HashMap<>();
  public void addMapEntry(String key, String value) {
    cachedValueMap.put(key, value);
  }
  public String retrieveValue(String key) {
    return cachedValueMap.get(key);
  }
  public Map<String, String> getCachedValueMap() {
    return cachedValueMap;
  }
  public void setCachedValueMap(Map<String, String> cachedValueMap) {
    this.cachedValueMap = cachedValueMap;
  }
}

A common use of application scoped CDI beans is to cache frequently used data so that we don’t have to hit a database every time we need to retrieve it. With standard application scoped CDI beans, we would have to have a copy of the bean in each node of a cluster; keeping those instances in sync would not be a trivial task. With Payara’s clustered CDI beans, there is a single instance shared across nodes.

As can be seen in the example, all we have to do to share an application scoped bean across nodes in a cluster is annotated with @Clustered and have it implement the Serializable interface; all the hard work to share the bean instance is done behind the scenes by Payara Micro.

Clustered application scoped CDI beans must be serializable; otherwise, our code will fail to deploy.

In order to use the @Clustered annotation, we need to add payara-api as a provided dependency to our application. When using Maven, we can do so by adding the appropriate dependency to the <dependencies> section of pom.xml .
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>fish.payara.api</groupId>
      <artifactId>payara-bom</artifactId>
      <version>${version.payara}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>fish.payara.api</groupId>
    <artifactId>payara-api</artifactId>
    <scope>provided</scope>
  </dependency>
  <!-- additional dependencies omitted-->
</dependencies>

When using the Payara Maven Bill of Materials (BOM), dependency versions are specified in the BOM; therefore, we shouldn’t specify them in our pom.xml.

To illustrate clustered application scoped CDI beans, we can write a simple RESTful web service that, in response to HTTP PUT and GET requests, adds or retrieves values to/from the Map in our clustered application scoped CDI bean.
package com.ensode.clusteredcdiappscopedbeans;
//imports omitted
@Path("cachedvalueaccessor")
public class CachedValueAccessorResource {
  @Inject
  private DataCache dataCache;
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String getCachedValue(@QueryParam("key") String key) {
    return dataCache.retrieveValue(key);
  }
  @PUT
  @Consumes(MediaType.TEXT_PLAIN)
  public void addCachedValue(@QueryParam("key") String key,
    @QueryParam("value") String value) {
    dataCache.addMapEntry(key, value);
  }
}

Notice that there is nothing special we need to do to use our clustered application scoped CDI bean; we simply inject it via the @Inject annotation as usual.

At this point, we have a simple but complete example we can use to illustrate clustered application scoped CDI beans; we simply package our application in a WAR file and deploy it to two separate instances of Payara Micro, which will automatically form a cluster.

We can do so from the command line as follows:
java -jar path/to/payara-micro-5.2021.10.jar
--contextroot clusteredapplicationbeansdemo --autoBindHttp
--deploy target/clustered-application-beans-demo-1.0-SNAPSHOT.war
If we examine Payara Micro’s output, we can see our application has been deployed and it is listening on the default 8080 HTTP port.
[2021-12-28T18:49:51.628-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640735391628] [levelValue: 800] [[
Payara Micro URLs:
http://127.0.0.1:8080/clusteredapplicationbeansdemo
'clustered-application-beans-demo-1.0-SNAPSHOT' REST Endpoints:
GET /clusteredapplicationbeansdemo/webresources/application.wadl
GET /clusteredapplicationbeansdemo/webresources/cachedvalueaccessor
PUT /clusteredapplicationbeansdemo/webresources/cachedvalueaccessor
]]
[2021-12-28T18:49:51.629-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640735391629] [levelValue: 800] Payara Micro  5 #badassmicrofish (build 879) ready in 7,708 (ms)

We then use the same command on a different terminal window to deploy a second copy of our WAR file.

On the second instance of Payara Micro, we should see output similar to the following:
[2021-12-28T18:55:32.268-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640735732268] [levelValue: 800] [[
Payara Micro URLs:
http://127.0.0.1:8081/clusteredapplicationbeansdemo
'clustered-application-beans-demo-1.0-SNAPSHOT' REST Endpoints:
GET /clusteredapplicationbeansdemo/webresources/application.wadl
GET /clusteredapplicationbeansdemo/webresources/cachedvalueaccessor
PUT /clusteredapplicationbeansdemo/webresources/cachedvalueaccessor
]]
[2021-12-28T18:55:32.268-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640735732268] [levelValue: 800] Payara Micro  5 #badassmicrofish (build 879) ready in 8,235 (ms)

Since we used the --autoBindHttp command-line argument for Payara Micro, the second instance listens for HTTP connections on the next available port (8081).

Additionally, there will be some output on the first instance of Payara Micro, which lets us know that the two instances formed a cluster; the output should look similar to the following:
[2021-12-28T18:55:28.239-0500] [] [INFO] [] [fish.payara.nucleus.cluster.PayaraCluster] [tid: _ThreadID=55 _ThreadName=hz.wonderful_sutherland.event-4] [timeMillis: 1640735728239] [levelValue: 800] Data Grid Instance Added f3fc7680-1fec-4544-8d9e-b8e7d20c634b at Address /192.168.1.165:6901
[2021-12-28T18:55:28.242-0500] [] [INFO] [] [fish.payara.nucleus.cluster.PayaraCluster] [tid: _ThreadID=55 _ThreadName=hz.wonderful_sutherland.event-4] [timeMillis: 1640735728242] [levelValue: 800] [[
  Data Grid Status
Payara Data Grid State: DG Version: 4 DG Name: development DG Size: 2
Instances: {
 DataGrid: development Name: Magnanimous-Butterfish Lite: false This: true UUID: 730a2023-0f47-4822-b2d9-d905ffcd0505 Address: /192.168.1.165:6900
 DataGrid: development Lite: false This: false UUID: f3fc7680-1fec-4544-8d9e-b8e7d20c634b Address: /192.168.1.165:6901
}]]

Recall that Payara Micro’s clustering capabilities are implemented via an in-memory data grid (Hazelcast); the output on the first instance of Payara Micro is letting us know that a data grid instance was added to the cluster.

If you refer to our example clustered application scoped CDI bean, you’ll notice the map holding the cached data is initially empty; we can add an entry to it by sending an HTTP PUT request to either instance of Payara Micro in the cluster. For example, using curl to send an HTTP PUT request to the first instance:
$curl -XPUT 'http://localhost:8080/clusteredapplicationbeansdemo/webresources/cachedvalueaccessor?key=foo&value=bar'

At this point, we added an entry to the Map in the clustered application scoped CDI bean with a key of foo and a value of bar. Since the bean is clustered, the change is reflected across all nodes in the cluster.

We can now retrieve the value we just added by sending an HTTP GET request to either Payara Micro instance in the cluster; to make it interesting, let’s send an HTTP GET request to the second instance in the cluster to retrieve the value we just added on the first instance:
$ curl http://localhost:8081/clusteredapplicationbeansdemo/webresources/cachedvalueaccessor?key=foo
bar

The text below the curl command is the response from the second instance; as we can see, we got the expected value, which was set by sending an HTTP PUT request to the first instance in the cluster.

Remote CDI Events

With Payara Micro, CDI events can be observed across the network by applications deployed in a different node from the application firing the event. To fire a remote CDI event, we need to annotate the event being fired with @Outbound and the parameter in the method handling the event with @Inbound. The following example illustrates how to fire a remote event; it is a modified version of the CDI events example we saw in Chapter 4, updated to fire remote CDI events.
package com.ensode.cdievents;
import com.ensode.cdievents.qualifier.Deleted;
import com.ensode.cdievents.qualifier.Updated;
import fish.payara.micro.cdi.Outbound;
@Path("countryservice")
public class CountryService {
  @Inject
  private @Updated @Outbound
  Event<Country> countryEvent;
  @Inject
  private @Deleted @Outbound
  Event<Country> countryDeletedEvent;
  @Inject
  private CountryLookup countryLookup;
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public Country handleGetRequest(@QueryParam("countryAbbrev") String countryAbbrev) {
    return countryLookup.getCountry(countryAbbrev);
  }
  @PUT
  @Consumes(MediaType.APPLICATION_JSON)
  public void updateCountry(Country country) {
    countryEvent.fire(country);
  }
  @DELETE
  @Consumes(MediaType.APPLICATION_JSON)
  public void deleteCountry(@QueryParam("countryAbbrev") String countryAbbrev) {
    Country country=countryLookup.getCountry(countryAbbrev);
    countryDeletedEvent.fire(country);
  }
}

The preceding RESTful web service code fires two types of country events: one when a country is updated and another when a country is deleted. Recall from Chapter 4 that we can use qualifiers to distinguish between events of the same type; in our example, the @Updated and @Deleted qualifiers are used to distinguish between these two events. In addition to our custom @Updated and @Deleted qualifiers, we annotated both events with the Payara-provided @Outbound qualifier; this qualifier is used to fire the event across the network so that event observer methods in other nodes can handle it. As a matter of fact, the only thing we have to do to convert a standard CDI event into a remote CDI event is to annotate the Event instance declaration with @Outbound.

For this example, we refactored the application so that the event observer is deployed in a separate WAR file; this way, we can deploy the observer to a different instance of Payara Micro.

Just like in Chapter 4, we have three observer methods across two different Java classes; we have a CountryManager class that has two observer methods: one for country events annotated with @Updated and another one for events annotated with @Deleted.
package com.ensode.cdi.remote.event.observer;
import com.ensode.cdievents.Country;
import com.ensode.cdievents.CountryLookup;
import com.ensode.cdievents.qualifier.Deleted;
import com.ensode.cdievents.qualifier.Updated;
import fish.payara.micro.cdi.Inbound;
//additional imports omitted
@ApplicationScoped
public class CountryManager {
  private static final Logger LOGGER =
    Logger.getLogger(CountryManager.class.getName());
  @Inject
  private CountryLookup countryLookup;
  public void updateCountry(
    @Observes @Inbound @Updated Country country) {
    LOGGER.log(Level.INFO, String.format(
      "Updating the following country: %s", country.getName()));
    countryLookup.updateCountry(country);
  }
  public void deleteCountry(
    @Observes @Inbound @Deleted Country country) {
    LOGGER.log(Level.INFO, String.format(
    "Deleting the following country: %s", country.getName()));
    countryLookup.deleteCountry(country);
  }
}

The only difference between this example and the corresponding example in Chapter 4 is the addition of the @Inbound annotation to the observer method parameter, which is needed to handle events fired across the network.

We have an additional class with an observer method; it listens for any country event (i.e., no custom qualifiers like @Updated or @Deleted are used) and simply sends output to the log on the observer method.
package com.ensode.cdi.remote.event.observer;
import com.ensode.cdievents.Country;
import fish.payara.micro.cdi.Inbound;
//additional imports omitted
@ApplicationScoped
public class CountryEventLogger {
  private static final Logger LOGGER = Logger.getLogger(CountryEventLogger.class.getName());
  public void logCountryEvent(
    @Observes @Inbound Country country) {
    LOGGER.log(Level.INFO, String.format(
     "Event fired for the following country: %s",
      country.getName()));
  }
}

Unsurprisingly, all we had to do to have this observer method listen for remote events is to annotate its parameter with the @Inbound annotation.

At this point, we are ready to test our refactored CDI event code; we deploy the WAR file containing the code to fire the events to an instance of Payara Micro listening on HTTP port 8080. Payara Micro’s output lets us know the endpoints we can use to send HTTP requests to our RESTful web service.
Payara Micro URLs:
http://127.0.0.1:8080/cdi-remote-events
'cdi-remote-events-1.0-SNAPSHOT' REST Endpoints:
GET    /cdi-remote-events/webresources/application.wadl
DELETE /cdi-remote-events/webresources/countryservice
GET    /cdi-remote-events/webresources/countryservice
PUT    /cdi-remote-events/webresources/countryservice
]]
We then deploy the second WAR file containing event observers to a second instance of Payara Micro listening on port 8081.
[2021-12-29T18:54:07.730-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640822047730] [levelValue: 800] [[
Payara Micro URLs:
http://127.0.0.1:8081/cdi-remote-events-observer
]]

Since we didn’t package any RESTful web services in the second WAR file (only CDI beans), no endpoints are displayed on the output of the second Payara Micro instance.

At this point, we are ready to test our code; we can send an HTTP DELETE request to the first Payara Micro instance, which will cause it to fire a remote CDI event; we can do so via a curl command as follows:
$ curl -XDELETE http://localhost:8080/cdi-remote-events/webresources/countryservice?countryAbbrev=AU
At this point, if we inspect the output of Payara Micro, we should see the log entries added by the appropriate CDI remote event observer methods.
[2021-12-29T18:59:21.152-0500] [] [INFO] [] [com.ensode.cdi.remote.event.observer.CountryEventLogger] [tid: _ThreadID=121 _ThreadName=concurrent/__defaultManagedExecutorService-managedThreadFactory-Thread-9] [timeMillis: 1640822361152] [levelValue: 800] Event fired for the following country: Australia
[2021-12-29T18:59:21.156-0500] [] [INFO] [] [com.ensode.cdi.remote.event.observer.CountryManager] [tid: _ThreadID=121 _ThreadName=concurrent/__defaultManagedExecutorService-managedThreadFactory-Thread-9] [timeMillis: 1640822361156] [levelValue: 800] Deleting the following country: Australia

As illustrated by our example, firing remote CDI events in Payara Micro takes very little work on our part; we simply annotate the event to be fired with @Outbound and the corresponding event observer method parameters with @Inbound; all the hard work is done by Payara Micro and is transparent to us as application developers.

Uber Jars

Uber Jars are executable JAR files that bundle both our application code and the Payara Micro runtime. Creating an Uber Jar can potentially simplify executing our applications; all we have to do is run the executable WAR file, without having to pass any arguments to Payara Micro.

Creating Uber Jars

We can create an Uber Jar by passing the --outputUberJar command-line option to Payara Micro, as in the following example:
java -jar path/to/payara-micro/5.2021.10/payara-micro-5.2021.10.jar --deploy target/cdi-remote-events-1.0-SNAPSHOT.war --autoBindHttp --outputUberJar cdiremoteevents.jar

The --outputUberJar command-line option takes an argument specifying the name of the JAR file to be created.

Most command-line arguments we pass to Payara Micro are recorded so that they don’t need to be specified when executing the JAR file; in our example, the generated JAR file will execute Payara Micro with the --autoBindHttp option enabled by default. One notable exception is that the --contextRoot command-line argument is not recorded; the context root of the generated JAR file will always be the name of the deployed WAR file (cdi-remote-events-1.0-SNAPSHOT.war in our example).

The preceding command generates a JAR file with the name we specified in the current directory; to execute it, we simply run the same way we run any executable JAR file:
java -jar cdiremoteevents.jar
We should see Payara Micro’s usual output on the terminal window:
[2021-12-31T09:30:37.148-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640961037148] [levelValue: 800] [[
Payara Micro URLs:
http://192.168.1.165:8080/cdi-remote-events-1.0-SNAPSHOT
'cdi-remote-events-1.0-SNAPSHOT' REST Endpoints:
GET    /cdi-remote-events-1.0-SNAPSHOT/webresources/application.wadl
DELETE /cdi-remote-events-1.0-SNAPSHOT/webresources/countryservice
GET    /cdi-remote-events-1.0-SNAPSHOT/webresources/countryservice
PUT    /cdi-remote-events-1.0-SNAPSHOT/webresources/countryservice
]]
[2021-12-31T09:30:37.148-0500] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1640961037148] [levelValue: 800] Payara Micro  5 #badassmicrofish (build 879) ready in 8,995 (ms)

If we wish to change the context root, we can rename the WAR file to have the context root name we want; for example, renaming cdi-remote-events-1.0-SNAPSHOT.war to cdiremoteevents.war will result in the application having a context root of /cdiremoteevents.

Embedding Payara Micro

We can embed Payara Micro inside other Java applications. Doing so allows us to start/stop/deploy apps, etc., programmatically via Payara Micro’s Java API. The following example illustrates how to do this:
package com.ensode.embeddedpayaramicroexample;
import fish.payara.micro.BootstrapException;
import fish.payara.micro.PayaraMicro;
import java.io.File;
public class Main {
  public void startPayaraMicro() throws BootstrapException {
    File warFile = new File("/path/to/some.war");
    PayaraMicro.getInstance().
      setHttpAutoBind(true).
      bootStrap().deploy("applicationname",
        "contextroot", warFile);
  }
  public static void main(String[] args)
    throws BootstrapException {
    new Main().startPayaraMicro();
  }
}

The PayaraMicro class is a singleton; it has a static getInstance() method that returns the PayaraMicro instance; we can configure Payara Micro programmatically by invoking setter and getter methods; for instance, in our example, we are invoking setHttpAutoBind(true), which is equivalent to passing the --autoBind command-line argument to Payara Micro when running from the command line. The API to configure Payara Micro is very intuitive for those familiar with running Payara Micro from the command line; refer to the official Payara Micro API JavaDoc at https://javadoc.io/doc/fish.payara.extras/payara-micro/latest/index.html; of particular interest is the fish.payara.micro package, particularly the PayaraMicro class and the PayaraMicroRuntime interface.

Once we have configured the embedded Payara Micro instance, we invoke its bootstrap() method, which returns an implementation of the PayaraMicroRuntime interface; to deploy our WAR file, we invoke its deploy() method, which takes three arguments: a String defining a name for our application, a second String defining the context root for the WAR file, and an instance of java.io.File representing our WAR file.

We can then run our application like any other stand-alone Java application. For Maven projects, the easiest way is to use the Exec Maven plug-in, which automatically sets the class path from our project dependencies; for example:
mvn exec:java -Dexec.mainClass="com.ensode.embeddedpayaramicroexample.Main"

would run our application, which in turn would start a Payara Micro instance; we can then send HTTP requests to this Payara Micro instance as usual.

Summary

In this chapter, we covered Payara Micro features that go beyond the Jakarta EE and MicroProfile standards. We explained how Payara Micro instances cluster automatically. We also covered how to share application scoped CDI beans across cluster nodes, as well as how to send CDI events across the network to listeners deployed to other Payara Micro instances on the cluster.

Additionally, we covered how to create so-called Uber Jars, which are executable JAR files that contain both our application code and the Payara Micro runtime bundled together, allowing us to run Payara Micro and deploy our code by running an executable JAR file.

We then covered how to embed Payara Micro in our Java applications and how we can configure and deploy our applications and start Payara Micro programmatically.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.255.44