Chapter 4. Develop Eclipse MicroProfile Applications for JBoss EAP
4.1. Maven and the JBoss EAP Eclipse MicroProfile Maven repository
4.1.1. Downloading the JBoss EAP Eclipse MicroProfile Maven repository patch as an archive file
Whenever an Eclipse MicroProfile Expansion Pack is released for JBoss EAP, a corresponding patch is provided for the JBoss EAP Eclipse MicroProfile Maven repository. This patch is provided as an incremental archive file that is extracted into the existing Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository. The incremental archive file does not overwrite or remove any existing files, so there is no rollback requirement.
Prerequisites
- You have set up an account on the Red Hat Customer Portal.
Procedure
- Open a browser and log in to the Red Hat Customer Portal.
- Select Downloads from the menu at the top of the page.
- Find the Red Hat JBoss Enterprise Application Platform entry in the list and select it.
- From the Product drop-down list, select JBoss EAP XP.
- From the Version drop-down list, select 2.0.0.
- Click the Releases tab.
- Find JBoss EAP XP 2.0.0 Incremental Maven Repository in the list, and then click Download.
- Save the archive file to your local directory.
Additional Resources
- To learn more about the JBoss EAP Maven repository, see About the Maven Repository in the JBoss EAP Development Guide.
4.1.2. Applying the JBoss EAP Eclipse MicroProfile Maven repository patch on your local system
You can install the JBoss EAP Eclipse MicroProfile Maven repository patch on your local file system.
When you apply a patch in the form of an incremental archive file to the repository, new files are added to this repository. The incremental archive file does not overwrite or remove any existing files on the repository, so there is no rollback requirement.
Prerequisites
You have downloaded and installed the Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository on your local system.
- Check that you have this minor version of the Red Hat JBoss Enterprise Application Platform 7.3 Maven repository installed on your local system.
- You have downloaded the JBoss EAP XP 2.0.0 Incremental Maven repository on your local system.
Procedure
-
Locate the path to your Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository. For example,
/path/to/repo/jboss-eap-7.3.0.GA-maven-repository/maven-repository/
. Extract the downloaded JBoss EAP XP 2.0.0 Incremental Maven repository directly into the directory of the Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository. For example, open a terminal and issue the following command, replacing the value for your Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository path:
$ unzip -o jboss-eap-xp-2.0.0-incremental-maven-repository.zip -d EAP_MAVEN_REPOSITORY_PATH
The EAP_MAVEN_REPOSITORY_PATH points to the jboss-eap-7.3.0.GA-maven-repository
. For example, this procedure demonstrated the use of the path /path/to/repo/jboss-eap-7.3.0.GA-maven-repository/
.
After you extract the JBoss EAP XP Incremental Maven repository into the Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository, the repository name becomes JBoss EAP Eclipse MicroProfile Maven repository.
Additional Resources
- To determine the URL of the JBoss EAP Maven repository, see Determining the URL for the JBoss EAP Maven repository in the JBoss EAP Development Guide.
4.1.3. Supported JBoss EAP Eclipse MicroProfile BOM
JBoss EAP XP 2.0.0 includes the JBoss EAP Eclipse MicroProfile BOM. This BOM is named jboss-eap-xp-microprofile
, and its use case supports JBoss EAP Eclipse MicroProfile APIs.
BOM Artifact ID | Use Case |
---|---|
jboss-eap-xp-microprofile |
This BOM, whose |
4.1.4. Using the JBoss EAP Eclipse MicroProfile Maven repository
You can access the jboss-eap-xp-microprofile
BOM after you install the Red Hat JBoss Enterprise Application Platform 7.3.0.GA Maven repository and apply the JBoss EAP XP Incremental Maven repository to it. The repository name then becomes JBoss EAP Eclipse MicroProfile Maven repository. The BOM is shipped inside the JBoss EAP XP Incremental Maven repository.
You must configure one of the following to use the JBoss EAP Eclipse MicroProfile Maven repository:
- The Maven global or user settings
- The project’s POM files
Maven settings used with a repository manager or repository on a shared server provide better control and manageability of projects.
You can use an alternative mirror to redirect all lookup requests for a specific repository to your repository manager without changing the project files.
Configuring the JBoss EAP Eclipse MicroProfile Maven repository by modifying the POM file overrides the global and user Maven settings for the configured project.
Prerequisites
- You have installed the Red Hat JBoss Enterprise Application Platform 7.3 Maven repository on your local system, and you have applied the JBoss EAP XP Incremental Maven repository to it.
Procedure
- Choose a configuration method and configure the JBoss EAP Eclipse MicroProfile Maven repository.
After you have configured the JBoss EAP Eclipse MicroProfile Maven repository, add the
jboss-eap-xp-microprofile
BOM to the project POM file. The following example shows how to configure the BOM in the<dependencyManagement>
section of thepom.xml
file:<dependencyManagement> <dependencies> ... <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>2.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> ... </dependencies> </dependencyManagement>
NoteIf you do not specify a value for the
type
element in thepom.xml
file, Maven specifies ajar
value for the element.
Additional Resources
- For more information about selecting methods to configure the JBoss EAP Maven repository, see Use the Maven Repository in the JBoss EAP Development Guide.
- For more information about managing dependencies, see Dependency Management.
4.2. Eclipse MicroProfile Config development
4.2.1. Creating a Maven project for Eclipse MicroProfile Config
Create a Maven project with the required dependencies and the directory structure for creating an Eclipse MicroProfile Config application.
Prerequisites
- Maven is installed.
Procedure
Set up the Maven project.
$ mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=microprofile-config \ -DinteractiveMode=false \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp cd microprofile-config
This creates the directory structure for the project and
pom.xml
configuration file.To let the POM file automatically manage the versions for the Eclipse MicroProfile Config artifact and the Eclipse MicroProfile REST Client artifact in the
jboss-eap-xp-microprofile
BOM, import the BOM to the<dependencyManagement>
section of the project POM file.<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>2.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Add the Eclipse MicroProfile Config artifact and the Eclipse MicroProfile REST Client artifact and other dependencies, managed by the BOM, to the
<dependency>
section of the project POM file. The following example demonstrates adding the Eclipse MicroProfile Config and the Eclipse MicroProfile REST Client dependencies to the file:<!-- Add the MicroProfile REST Client API. Set
provided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>org.eclipse.microprofile.rest.client</groupId> <artifactId>microprofile-rest-client-api</artifactId> <scope>provided</scope> </dependency> <!-- Add the MicroProfile Config API. Setprovided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>org.eclipse.microprofile.config</groupId> <artifactId>microprofile-config-api</artifactId> <scope>provided</scope> </dependency> <!-- Add the JAX-RS API. Setprovided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_2.1_spec</artifactId> <scope>provided</scope> </dependency> <!-- Add the CDI API. Setprovided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <scope>provided</scope> </dependency>
4.2.2. Using MicroProfile Config property in an application
Create an application that uses a configured ConfigSource
.
Prerequisites
- Eclipse MicroProfile Config is enabled in JBoss EAP.
- The latest POM is installed.
- The Maven project is configured for creating an Eclipse MicroProfile Config application.
Procedure
Create the directory to store class files:
$ mkdir -p APPLICATION_ROOT/src/main/java/com/example/microprofile/config/
Where
APPLICATION_ROOT
is the directory containing thepom.xml
configuration file for the application.Navigate to the new directory:
$ cd APPLICATION_ROOT/src/main/java/com/example/microprofile/config/
Create all class files described in this procedure in this directory.
Create a class file named
HelloApplication.java
with the following content:package com.example.microprofile.config; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/") public class HelloApplication extends Application { }
This class defines the application as a JAX-RS application.
Create a class file named
HelloService.java
with the following content:package com.example.microprofile.config; public class HelloService { String createHelloMessage(String name){ return "Hello " + name; } }
Create a class file named
HelloWorld.java
with the following content:package com.example.microprofile.config; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.eclipse.microprofile.config.inject.ConfigProperty; @Path("/config") public class HelloWorld { @Inject @ConfigProperty(name="name", defaultValue="jim") 1 String name; @Inject HelloService helloService; @GET @Path("/json") @Produces({ "application/json" }) public String getHelloWorldJSON() { String message = helloService.createHelloMessage(name); return "{\"result\":\"" + message + "\"}"; } }
- 1
- A MicroProfile Config property is injected into the class with the annotation
@ConfigProperty(name="name", defaultValue="jim")
. If noConfigSource
is configured, the valuejim
is returned.
Create an empty file named
beans.xml
in thesrc/main/webapp/WEB-INF/
directory:$ touch APPLICATION_ROOT/src/main/webapp/WEB-INF/beans.xml
Where
APPLICATION_ROOT
is the directory containing thepom.xml
configuration file for the application.Navigate to the root directory of the application:
$ cd APPLICATION_ROOT
Where
APPLICATION_ROOT
is the directory containing thepom.xml
configuration file for the application.Build the project:
$ mvn clean install wildfly:deploy
Test the output:
$ curl http://localhost:8080/microprofile-config/config/json
The following is the expected output:
{"result":"Hello jim"}
4.3. Eclipse MicroProfile Fault Tolerance application development
4.3.1. Adding the MicroProfile Fault Tolerance extension
The MicroProfile Fault Tolerance extension is included in standalone-microprofile.xml
and standalone-microprofile-ha.xml
configurations that are provided as part of JBoss EAP XP.
The extension is not included in the standard standalone.xml
configuration. To use the extension, you must manually enable it.
Prerequisites
- EAP XP pack is installed.
Procedure
Add the MicroProfile Fault Tolerance extension using the following management CLI command:
/extension=org.wildfly.extension.microprofile.fault-tolerance-smallrye:add
Enable the
microprofile-fault-tolerance-smallrye
subsystem using the following managenent command:/subsystem=microprofile-fault-tolerance-smallrye:add
Reload the server with the following management command:
reload
4.3.2. Configuring Maven project for Eclipse MicroProfile Fault Tolerance
Create a Maven project with the required dependencies and the directory structure for creating an Eclipse MicroProfile Fault Tolerance application.
Prerequisites
- Maven is installed.
Procedure
Set up the Maven project:
mvn archetype:generate \ -DgroupId=com.example.microprofile.faulttolerance \ -DartifactId=microprofile-fault-tolerance \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp \ -DinteractiveMode=false cd microprofile-fault-tolerance
The command creates the directory structure for the project and the
pom.xml
configuration file.To let the POM file automatically manage the versions for the Eclipse MicroProfile Fault Tolerance artifact in the
jboss-eap-xp-microprofile
BOM, import the BOM to the<dependencyManagement>
section of the project POM file.<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>${version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Replace ${version.microprofile.bom} with the installed version of BOM.
Add the Eclipse MicroProfile Fault Tolerance artifact, managed by the BOM, to the
<dependency>
section of the project POM file. The following example demonstrates adding the Eclipse MicroProfile Fault Tolerance dependency to the file:<!-- Add the MicroProfile Fault Tolerance API. Set
provided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>org.eclipse.microprofile.fault.tolerance</groupId> <artifactId>microprofile-fault-tolerance-api</artifactId> <scope>provided</scope> </dependency>
4.3.3. Creating a fault tolerant application
Create a fault-tolerant application that implements retry, timeout, and fallback patterns for fault tolerance.
Prerequisites
- Maven dependencies have been configured.
Procedure
Create the directory to store class files:
$ mkdir -p APPLICATION_ROOT/src/main/java/com/example/microprofile/faulttolerance
APPLICATION_ROOT is the directory containing the
pom.xml
configuration file for the application.Navigate to the new directory:
$ cd APPLICATION_ROOT/src/main/java/com/example/microprofile/faulttolerance
For the following steps, create all class files in the new directory.
Create a simple entity representing a coffee sample as
Coffee.java
with the following content:package com.example.microprofile.faulttolerance; public class Coffee { public Integer id; public String name; public String countryOfOrigin; public Integer price; public Coffee() { } public Coffee(Integer id, String name, String countryOfOrigin, Integer price) { this.id = id; this.name = name; this.countryOfOrigin = countryOfOrigin; this.price = price; } }
Create a class file
CoffeeApplication.java
with the following content:package com.example.microprofile.faulttolerance; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/") public class CoffeeApplication extends Application { }
Create a CDI Bean as
CoffeeRepositoryService.java
with the following content:package com.example.microprofile.faulttolerance; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class CoffeeRepositoryService { private Map<Integer, Coffee> coffeeList = new HashMap<>(); public CoffeeRepositoryService() { coffeeList.put(1, new Coffee(1, "Fernandez Espresso", "Colombia", 23)); coffeeList.put(2, new Coffee(2, "La Scala Whole Beans", "Bolivia", 18)); coffeeList.put(3, new Coffee(3, "Dak Lak Filter", "Vietnam", 25)); } public List<Coffee> getAllCoffees() { return new ArrayList<>(coffeeList.values()); } public Coffee getCoffeeById(Integer id) { return coffeeList.get(id); } public List<Coffee> getRecommendations(Integer id) { if (id == null) { return Collections.emptyList(); } return coffeeList.values().stream() .filter(coffee -> !id.equals(coffee.id)) .limit(2) .collect(Collectors.toList()); } }
Create a class file
CoffeeResource.java
with the following content:package com.example.microprofile.faulttolerance; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.util.Collections; import javax.ws.rs.PathParam; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Timeout; import org.eclipse.microprofile.faulttolerance.Retry; @Path("/coffee") @Produces(MediaType.APPLICATION_JSON) public class CoffeeResource { @Inject private CoffeeRepositoryService coffeeRepository; private AtomicLong counter = new AtomicLong(0); @GET @Retry(maxRetries = 4) 1 public List<Coffee> coffees() { final Long invocationNumber = counter.getAndIncrement(); return coffeeRepository.getAllCoffees(); } @GET @Path("/{id}/recommendations") @Timeout(250) 2 public List<Coffee> recommendations(@PathParam("id") int id) { return coffeeRepository.getRecommendations(id); } @GET @Path("fallback/{id}/recommendations") @Fallback(fallbackMethod = "fallbackRecommendations") 3 public List<Coffee> recommendations2(@PathParam("id") int id) { return coffeeRepository.getRecommendations(id); } public List<Coffee> fallbackRecommendations(int id) { //always return a default coffee return Collections.singletonList(coffeeRepository.getCoffeeById(1)); } }
Navigate to the root directory of the application:
$ cd APPLICATION_ROOT
Build the application using the following Maven command:
$ mvn clean install wildfly:deploy
Access the application at
http://localhost:8080/microprofile-fault-tolerance/coffee
.
Additional Resources
-
For a detailed example of fault tolerant application, which includes artificial failures to test the fault tolerance of the application, see the
microprofile-fault-tolerance
quickstart.
4.4. Eclipse MicroProfile Health development
4.4.1. Custom health check example
The default implementation provided by the microprofile-health-smallrye
subsystem performs a basic health check. For more detailed information, on either the server or application status, custom health checks may be included. Any CDI beans that include the org.eclipse.microprofile.health.Health
annotation at the class level are automatically discovered and invoked at runtime.
The following example demonstrates how to create a new implementation of a health check that returns an UP
state.
import org.eclipse.microprofile.health.Health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; @Health public class HealthTest implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("health-test").up().build(); } }
Once deployed, any subsequent health check queries include the custom checks, as demostrated in the following example.
/subsystem=microprofile-health-smallrye:check { "outcome" => "success", "result" => { "outcome" => "UP", "checks" => [{ "name" => "health-test", "state" => "UP" }] } }
4.4.2. The @Liveness annotation example
The following is an example of using the @Liveness
annotation in an application.
@Liveness @ApplicationScoped public class DataHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("Health check with data") .up() .withData("foo", "fooValue") .withData("bar", "barValue") .build(); } }
4.4.3. The @Readiness annotation example
The following example demonstrates checking connection to a database. If the database is down, the readiness check reports error.
@Readiness @ApplicationScoped public class DatabaseConnectionHealthCheck implements HealthCheck { @Inject @ConfigProperty(name = "database.up", defaultValue = "false") private boolean databaseUp; @Override public HealthCheckResponse call() { HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("Database connection health check"); try { simulateDatabaseConnectionVerification(); responseBuilder.up(); } catch (IllegalStateException e) { // cannot access the database responseBuilder.down() .withData("error", e.getMessage()); // pass the exception message } return responseBuilder.build(); } private void simulateDatabaseConnectionVerification() { if (!databaseUp) { throw new IllegalStateException("Cannot contact database"); } } }
4.5. Eclipse MicroProfile JWT application development
4.5.1. Enabling microprofile-jwt-smallrye
subsystem
The Eclipse MicroProfile JWT integration is provided by the microprofile-jwt-smallrye
subsystem and is included in the default configuration. If the subsystem is not present in the default configuration, you can add it as follows.
Prerequisites
- EAP XP is installed.
Procedure
Enable the MicroProfile JWT smallrye extension in JBoss EAP:
/extension=org.wildfly.extension.microprofile.jwt-smallrye:add
Enable the
microprofile-jwt-smallrye
subsystem:/subsystem=microprofile-jwt-smallrye:add
Reload the server:
reload
The microprofile-jwt-smallrye
subsystem is enabled.
4.5.2. Configuring Maven project for developing JWT applications
Create a Maven project with the required dependencies and the directory structure for developing a JWT application.
Prerequisites
- Maven is installed.
-
microprofile-jwt-smallrye
subsystem is enabled.
Procedure
Set up the maven project:
$ mvn archetype:generate -DinteractiveMode=false \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp \ -DgroupId=com.example -DartifactId=microprofile-jwt \ -Dversion=1.0.0.Alpha1-SNAPSHOT cd microprofile-jwt
The command creates the directory structure for the project and the
pom.xml
configuration file.To let the POM file automatically manage the versions for the Eclipse MicroProfile JWT artifact in the
jboss-eap-xp-microprofile
BOM, import the BOM to the<dependencyManagement>
section of the project POM file.<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>${version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Replace ${version.microprofile.bom} with the installed version of BOM.
Add the Eclipse MicroProfile JWT artifact, managed by the BOM, to the
<dependency>
section of the project POM file. The following example demonstrates adding the Eclipse MicroProfile JWT dependency to the file:<!-- Add the MicroProfile JWT API. Set
provided
for the<scope>
tag, as the API is included in the server. --> <dependency> <groupId>org.eclipse.microprofile.jwt</groupId> <artifactId>microprofile-jwt-auth-api</artifactId> <scope>provided</scope> </dependency>
4.5.3. Creating an application with Eclipse MicroProfile JWT
Create an application that authenticates requests based on JWT tokens and implements authorization based on the identity of the token bearer.
The following procedure provides code for generating tokens as an example. You should implement your own token generator.
Prerequisites
- Maven project is configured with the correct dependencies.
Procedure
Create a token generator.
This step serves as a reference. For a production environment, implement your own token generator.
Create a directory
src/test/java
for token the generator utility and navigate to it:$ mkdir -p src/test/java $ cd src/test/java
Create a class file
TokenUtil.java
with the following content:package com.example.mpjwt; import java.io.FileInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.UUID; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.RSASSASigner; public class TokenUtil { private static PrivateKey loadPrivateKey(final String fileName) throws Exception { try (InputStream is = new FileInputStream(fileName)) { byte[] contents = new byte[4096]; int length = is.read(contents); String rawKey = new String(contents, 0, length, StandardCharsets.UTF_8) .replaceAll("-----BEGIN (.*)-----", "") .replaceAll("-----END (.*)----", "") .replaceAll("\r\n", "").replaceAll("\n", "").trim(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(rawKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } } public static String generateJWT(final String principal, final String birthdate, final String...groups) throws Exception { PrivateKey privateKey = loadPrivateKey("private.pem"); JWSSigner signer = new RSASSASigner(privateKey); JsonArrayBuilder groupsBuilder = Json.createArrayBuilder(); for (String group : groups) { groupsBuilder.add(group); } long currentTime = System.currentTimeMillis() / 1000; JsonObjectBuilder claimsBuilder = Json.createObjectBuilder() .add("sub", principal) .add("upn", principal) .add("iss", "quickstart-jwt-issuer") .add("aud", "jwt-audience") .add("groups", groupsBuilder.build()) .add("birthdate", birthdate) .add("jti", UUID.randomUUID().toString()) .add("iat", currentTime) .add("exp", currentTime + 14400); JWSObject jwsObject = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256) .type(new JOSEObjectType("jwt")) .keyID("Test Key").build(), new Payload(claimsBuilder.build().toString())); jwsObject.sign(signer); return jwsObject.serialize(); } public static void main(String[] args) throws Exception { if (args.length < 2) throw new IllegalArgumentException("Usage TokenUtil {principal} {birthdate} {groups}"); String principal = args[0]; String birthdate = args[1]; String[] groups = new String[args.length - 2]; System.arraycopy(args, 2, groups, 0, groups.length); String token = generateJWT(principal, birthdate, groups); String[] parts = token.split("\\."); System.out.println(String.format("\nJWT Header - %s", new String(Base64.getDecoder().decode(parts[0]), StandardCharsets.UTF_8))); System.out.println(String.format("\nJWT Claims - %s", new String(Base64.getDecoder().decode(parts[1]), StandardCharsets.UTF_8))); System.out.println(String.format("\nGenerated JWT Token \n%s\n", token)); } }
Create the
web.xml
file in thesrc/main/webapp/WEB-INF
directory with the following content:<context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> <security-role> <role-name>Subscriber</role-name> </security-role>
Create a class file
SampleEndPoint.java
with the following content:package com.example.mpjwt; import javax.ws.rs.GET; import javax.ws.rs.Path; import java.security.Principal; import javax.ws.rs.core.Context; import javax.ws.rs.core.SecurityContext; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import java.time.LocalDate; import java.time.Period; import java.util.Optional; import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/Sample") public class SampleEndPoint { @GET @Path("/helloworld") public String helloworld(@Context SecurityContext securityContext) { Principal principal = securityContext.getUserPrincipal(); String caller = principal == null ? "anonymous" : principal.getName(); return "Hello " + caller; } @Inject JsonWebToken jwt; @GET() @Path("/subscription") @RolesAllowed({"Subscriber"}) public String helloRolesAllowed(@Context SecurityContext ctx) { Principal caller = ctx.getUserPrincipal(); String name = caller == null ? "anonymous" : caller.getName(); boolean hasJWT = jwt.getClaimNames() != null; String helloReply = String.format("hello + %s, hasJWT: %s", name, hasJWT); return helloReply; } @Inject @Claim(standard = Claims.birthdate) Optional<String> birthdate; @GET() @Path("/birthday") @RolesAllowed({ "Subscriber" }) public String birthday() { if (birthdate.isPresent()) { LocalDate birthdate = LocalDate.parse(this.birthdate.get().toString()); LocalDate today = LocalDate.now(); LocalDate next = birthdate.withYear(today.getYear()); if (today.equals(next)) { return "Happy Birthday"; } if (next.isBefore(today)) { next = next.withYear(next.getYear() + 1); } Period wait = today.until(next); return String.format("%d months and %d days until your next birthday.", wait.getMonths(), wait.getDays()); } return "Sorry, we don't know your birthdate."; } }
The methods annotated with
@Path
are the JAX-RS endpoints.The annotation
@Claim
defines a JWT claim.Create a class file
App.java
to enable JAX-RS:package com.example.mpjwt; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import org.eclipse.microprofile.auth.LoginConfig; @ApplicationPath("/rest") @LoginConfig(authMethod="MP-JWT", realmName="MP JWT Realm") public class App extends Application {}
The annotation
@LoginConfig(authMethod="MP-JWT", realmName="MP JWT Realm")
enables JWT RBAC during deployment.Compile the application with the following Maven command:
$ mvn package
Generate JWT token using the token generator utility:
$ mvn exec:java -Dexec.mainClass=org.wildfly.quickstarts.mpjwt.TokenUtil -Dexec.classpathScope=test -Dexec.args="testUser 2017-09-15 Echoer Subscriber"
Build and deploy the application using the following Maven command:
$ mvn package wildfly:deploy
Test the application.
Call the
Sample/subscription
endpoint using the bearer token:$ curl -H "Authorization: Bearer ey..rg" http://localhost:8080/microprofile-jwt/rest/Sample/subscription
Call the
Sample/birthday
endpoint:$ curl -H "Authorization: Bearer ey..rg" http://localhost:8080/microprofile-jwt/rest/Sample/birthday
4.6. Eclipse MicroProfile Metrics development
4.6.1. Creating an Eclipse MicroProfile Metrics application
Create an application that returns the number of requests made to the application.
Procedure
Create a class file
HelloService.java
with the following content:package com.example.microprofile.metrics; public class HelloService { String createHelloMessage(String name){ return "Hello" + name; } }
Create a class file
HelloWorld.java
with the following content:package com.example.microprofile.metrics; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.eclipse.microprofile.metrics.annotation.Counted; @Path("/") public class HelloWorld { @Inject HelloService helloService; @GET @Path("/json") @Produces({ "application/json" }) @Counted(name = "requestCount", absolute = true, description = "Number of times the getHelloWorldJSON was requested") public String getHelloWorldJSON() { return "{\"result\":\"" + helloService.createHelloMessage("World") + "\"}"; } }
Update the
pom.xml
file to include the following dependency:<dependency> <groupId>org.eclipse.microprofile.metrics</groupId> <artifactId>microprofile-metrics-api</artifactId> <scope>provided</scope> </dependency>
Build the application using the following Maven command:
$ mvn clean install wildfly:deploy
Test the metrics:
Issue the following command in the CLI:
$ curl -v http://localhost:9990/metrics | grep request_count | grep helloworld-rs-metrics
Expected output:
jboss_undertow_request_count_total{deployment="helloworld-rs-metrics.war",servlet="org.jboss.as.quickstarts.rshelloworld.JAXActivator",subdeployment="helloworld-rs-metrics.war",microprofile_scope="vendor"} 0.0
- In a browser, navigate to the URL http://localhost:8080/helloworld-rs/rest/json.
Re-Issue the following command in the CLI:
$ curl -v http://localhost:9990/metrics | grep request_count | grep helloworld-rs-metrics
Expected output:
jboss_undertow_request_count_total{deployment="helloworld-rs-metrics.war",servlet="org.jboss.as.quickstarts.rshelloworld.JAXActivator",subdeployment="helloworld-rs-metrics.war",microprofile_scope="vendor"} 1.0
4.7. Developing an Eclipse MicroProfile OpenAPI application
4.7.1. Enabling Eclipse MicroProfile OpenAPI
The microprofile-openapi-smallrye
subsystem is provided in the standalone-microprofile.xml
configuration. However, JBoss EAP XP uses the standalone.xml
by default. You must include the subsystem in standalone.xml
to use it.
Alternatively, you can follow the procedure Updating standalone configurations with Eclipse MicroProfile subsystems and extensions to update the standalone.xml
configuration file.
Procedure
Enable the MicroProfile OpenAPI smallrye extension in JBoss EAP:
/extension=org.wildfly.extension.microprofile.openapi-smallrye:add()
Enable the
microprofile-openapi-smallrye
subsystem using the following management command:/subsystem=microprofile-openapi-smallrye:add()
Reload the server.
reload
The microprofile-openapi-smallrye
subsystem is enabled.
4.7.2. Configuring Maven project for Eclipse MicroProfile OpenAPI
Create a Maven project to set up the dependencies for creating an Eclipse MicroProfile OpenAPI application.
Prerequisites
- Maven is installed.
- JBoss EAP Maven repository is configured.
Procedure
Initialize the project:
mvn archetype:generate \ -DgroupId=com.example.microprofile.openapi \ -DartifactId=microprofile-openapi\ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp \ -DinteractiveMode=false cd microprofile-openapi
The command creates the directory structure for the project and the
pom.xml
configuration file.Edit the
pom.xml
configuration file to contain:<?xml version="1.0" encoding="UTF-8"?> <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.example.microprofile.openapi</groupId> <artifactId>microprofile-openapi</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>microprofile-openapi Maven Webapp</name> <!-- Update the value with the URL of the project --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <version.server.bom>2.0.0.GA</version.server.bom> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>${version.server.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_2.1_spec</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <!-- Set the name of the archive --> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> <!-- Allows to use mvn wildfly:deploy --> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Use the
pom.xml
configuration file and directory structure to create an application.
Additional resources
- For information about configuring the JBoss EAP Maven repository, see Configuring the JBoss EAP Maven repository with the POM file.
4.7.3. Creating an Eclipse MicroProfile OpenAPI application
Create an application that returns an OpenAPI v3 document.
Prerequisites
- Maven project is configured for creating an Eclipse MicroProfile OpenAPI application.
Procedure
Create the directory to store class files:
$ mkdir -p APPLICATION_ROOT/src/main/java/com/example/microprofile/openapi/
APPLICATION_ROOT is the directory containing the
pom.xml
configuration file for the application.Navigate to the new directory:
$ cd APPLICATION_ROOT/src/main/java/com/example/microprofile/openapi/
All the class files in the following steps must be created in this directory.
Create the class file
InventoryApplication.java
with the following content:package com.example.microprofile.openapi; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/inventory") public class InventoryApplication extends Application { }
This class serves as the REST endpoint for the application.
Create a class file
Fruit.java
with the following content:package com.example.microprofile.openapi; public class Fruit { private final String name; private final String description; public Fruit(String name, String description) { this.name = name; this.description = description; } public String getName() { return this.name; } public String getDescription() { return this.description; } }
Create a class file
FruitResource.java
with the following content:package com.example.microprofile.openapi; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/fruit") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class FruitResource { private final Set<Fruit> fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>())); public FruitResource() { this.fruits.add(new Fruit("Apple", "Winter fruit")); this.fruits.add(new Fruit("Pineapple", "Tropical fruit")); } @GET public Set<Fruit> all() { return this.fruits; } @POST public Set<Fruit> add(Fruit fruit) { this.fruits.add(fruit); return this.fruits; } @DELETE public Set<Fruit> remove(Fruit fruit) { this.fruits.removeIf(existingFruit -> existingFruit.getName().contentEquals(fruit.getName())); return this.fruits; } }
Navigate to the root directory of the application:
$ cd APPLICATION_ROOT
Build and deploy the application using the following Maven command:
$ mvn wildfly:deploy
Test the application.
Access the OpenAPI documentation of the sample application using
curl
:$ curl http://localhost:8080/openapi
The following output is returned:
openapi: 3.0.1 info: title: Archetype Created Web Application version: "1.0" servers: - url: /microprofile-openapi paths: /inventory/fruit: get: responses: "200": description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Fruit' post: requestBody: content: application/json: schema: $ref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Fruit' delete: requestBody: content: application/json: schema: $ref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Fruit' components: schemas: Fruit: type: object properties: description: type: string name: type: string
Additional Resources
- For a list of annotations defined in MicroProfile SmallRye OpenAPI, see MicroProfile OpenAPI annotations.
4.7.4. Configuring JBoss EAP to serve a static OpenAPI document
Configure JBoss EAP to serve a static OpenAPI document that describes the REST services for the host.
When JBoss EAP is configured to serve a static OpenAPI document, the static OpenAPI document is processed before any JAX-RS and MicroProfile OpenAPI annotations.
In a production environment, disable annotation processing when serving a static document. Disabling annotation processing ensures that an immutable and versioned API contract is available for clients.
Procedure
Create a directory in the application source tree:
$ mkdir APPLICATION_ROOT/src/main/webapp/META-INF
APPLICATION_ROOT is the directory containing the
pom.xml
configuration file for the application.Query the OpenAPI endpoint, redirecting the output to a file:
$ curl http://localhost:8080/openapi?format=JSON > src/main/webapp/META-INF/openapi.json
By default, the endpoint serves a YAML document,
format=JSON
specifies that a JSON document is returned.Configure the application to skip annotation scanning when processing the OpenAPI document model:
$ echo "mp.openapi.scan.disable=true" > APPLICATION_ROOT/src/main/webapp/META-INF/microprofile-config.properties
Rebuild the application:
$ mvn clean install
Deploy the application again using the following management CLI commands:
Undeploy the application:
undeploy microprofile-openapi.war
Deploy the application:
deploy APPLICATION_ROOT/target/microprofile-openapi.war
JBoss EAP now serves a static OpenAPI document at the OpenAPI endpoint.
4.8. Eclipse MicroProfile REST Client development
4.8.1. A comparison between MicroProfile REST client and JAX-RS syntaxes
The MicroProfile REST client enables a version of distributed object communication, which is also implemented in CORBA, Java Remote Method Invocation (RMI), the JBoss Remoting Project, and RESTEasy. For example, consider the resource:
@Path("resource") public class TestResource { @Path("test") @GET String test() { return "test"; } }
The following example demonstrates using the JAX-RS native way to access the TestResource
class:
Client client = ClientBuilder.newClient(); String response = client.target("http://localhost:8081/test").request().get(String.class);
However, Microprofile REST client supports a more intuitive syntax by directly calling the test()
method as the following example demonstrates:
@Path("resource") public interface TestResourceIntf { @Path("test") @GET public String test(); } TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .build(TestResourceIntf.class); String s = service.test();
In the preceding example, making calls on the TestResource
class becomes much easier with the TestResourceIntf
class, as illustrated by the call service.test()
.
The following example is a more elaborate version of the TestResourceIntf
class:
@Path("resource") public interface TestResourceIntf2 { @Path("test/{path}")mes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); }
Calling the service.test("p", "q", "e")
method results in an HTTP message as shown in the following example:
POST /resource/test/p/?query=q HTTP/1.1 Accept: text/html Content-Type: text/plain Content-Length: 1 e
4.8.2. Programmatic registration of providers in MicroProfile REST client
With the MicroProfile REST client, you can configure the client environment by registering providers. For example:
TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .register(MyClientResponseFilter.class) .register(MyMessageBodyReader.class) .build(TestResourceIntf.class);
4.8.3. Declarative registration of providers in MicroProfile REST client
Use the MicroProfile REST client to register providers declaratively by adding the org.eclipse.microprofile.rest.client.annotation.RegisterProvider
annotation to the target interface, as shown in the following example:
@Path("resource") @RegisterProvider(MyClientResponseFilter.class) @RegisterProvider(MyMessageBodyReader.class) public interface TestResourceIntf2 { @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); }
Declaring the MyClientResponseFilter
class and the MyMessageBodyReader
class with annotations eliminates the need to call the RestClientBuilder.register()
method.
4.8.4. Declarative specification of headers in MicroProfile REST client
You can specify a header for an HTTP request in the following ways:
- By annotating one of the resource method parameters.
-
By declaratively using the
org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam
annotation.
The following example illustrates setting a header by annotating one of the resource method parameters with the annotation @HeaderValue
:
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) String contentLang(@HeaderParam(HttpHeaders.CONTENT_LANGUAGE) String contentLanguage, String subject);
The following example illustrates setting a header using the org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam
annotation:
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) @ClientHeaderParam(name=HttpHeaders.CONTENT_LANGUAGE, value="{getLanguage}") String contentLang(String subject); default String getLanguage() { return ...; }
4.8.5. Propagation of headers on the server in MicroProfile REST client
An instance of org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory
, if activated, can do a bulk transfer of incoming headers to an outgoing request. The default instance org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl
returns a map consisting of those incoming headers that are listed in the comma-separated configuration property org.eclipse.microprofile.rest.client.propagateHeaders
.
The following are the rules for instantiating the ClientHeadersFactory
interface:
-
A
ClientHeadersFactory
instance invoked in the context of a JAX-RS request can support injection of fields and methods annotated with@Context
. -
A
ClientHeadersFactory
instance that is managed by CDI must use the appropriate CDI-managed instance. It must also support the@Inject
injection.
The org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory
interface is defined as follows:
public interface ClientHeadersFactory { /** * Updates the HTTP headers to send to the remote service. Note that providers * on the outbound processing chain could further update the headers. * * @param incomingHeaders - the map of headers from the inbound JAX-RS request. This will * be an empty map if the associated client interface is not part of a JAX-RS request. * @param clientOutgoingHeaders - the read-only map of header parameters specified on the * client interface. * @return a map of HTTP headers to merge with the clientOutgoingHeaders to be sent to * the remote service. */ MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders); }
Additional resources
4.8.6. ResponseExceptionMapper in MicroProfile REST client
The org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper
class is the client-side inverse of the javax.ws.rs.ext.ExceptionMapper
class, which is defined in JAX-RS. The ExceptionMapper.toResponse()
method turns an Exception
class thrown during the server-side processing into a Response
class. The ResponseExceptionMapper.toThrowable()
method turns a Response
class received on the client-side with an HTTP error status into an Exception
class.
You can register the ResponseExceptionMapper
class either programmatically or declaratively. In the absence of a registered ResponseExceptionMapper
class, a default ResponseExceptionMapper
class maps any response with status >= 400
to a WebApplicationException
class.
4.8.7. Context dependency injection with MicroProfile REST client
In MicroProfile REST client, you must annotate any interface that is managed as a CDI bean with the @RegisterRestClient
class. For example:
@Path("resource") @RegisterProvider(MyClientResponseFilter.class) public static class TestResourceImpl { @Inject TestDataBase db; @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity) { return db.getByName(query); } } @Path("database") @RegisterRestClient public interface TestDataBase { @Path("") @POST public String getByName(String name); }
Here, the MicroProfile REST client implementation creates a client for a TestDataBase
class service, allowing easy access by the TestResourceImpl
class. However, it does not include the information about the path to the TestDataBase
class implementation. This information can be supplied by the optional @RegisterProvider
parameter baseUri
:
@Path("database") @RegisterRestClient(baseUri="https://localhost:8080/webapp") public interface TestDataBase { @Path("") @POST public String getByName(String name); }
This indicates that you can access the implementation of TestDataBase
at https://localhost:8080/webapp. You can also supply the information externally with the following system variable:
<fully qualified name of TestDataBase>/mp-rest/url=<URL>
For example, the following command indicates that you can access an implementation of the com.bluemonkeydiamond.TestDatabase
class at https://localhost:8080/webapp:
com.bluemonkeydiamond.TestDatabase/mp-rest/url=https://localhost:8080/webapp