Chapter 4. Develop MicroProfile Applications for JBoss EAP 4.1. Maven and the JBoss EAP MicroProfile Maven repository 4.1.1. Downloading the JBoss EAP MicroProfile Maven repository patch as an archive file Whenever an MicroProfile Expansion Pack is released for JBoss EAP, a corresponding patch is provided for the JBoss EAP 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.4.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 4.0.0 . Click the Releases tab. Find JBoss EAP XP 4.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 MicroProfile Maven repository patch on your local system You can install the JBoss EAP 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.4.0.GA Maven repository on your local system. Check that you have this minor version of the Red Hat JBoss Enterprise Application Platform 7.4 Maven repository installed on your local system. You have downloaded the JBoss EAP XP 4.0.0 Incremental Maven Repository on your local system. Procedure Locate the path to your Red Hat JBoss Enterprise Application Platform 7.4.0.GA Maven repository. For example, /path/to/repo/jboss-eap-7.4.0.GA-maven-repository/maven-repository/ . Extract the downloaded JBoss EAP XP 4.0.0 Incremental Maven Repository directly into the directory of the Red Hat JBoss Enterprise Application Platform 7.4.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.4.0.GA Maven repository path: Note The EAP_MAVEN_REPOSITORY_PATH points to the jboss-eap-7.4.0.GA-maven-repository . For example, this procedure demonstrated the use of the path /path/to/repo/jboss-eap-7.4.0.GA-maven-repository/ . After you extract the JBoss EAP XP Incremental Maven repository into the Red Hat JBoss Enterprise Application Platform 7.4.0.GA Maven repository, the repository name becomes JBoss EAP 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 MicroProfile BOM JBoss EAP XP 4.0.0 includes the JBoss EAP MicroProfile BOM. This BOM is named jboss-eap-xp-microprofile , and its use case supports JBoss EAP MicroProfile APIs. Table 4.1. JBoss EAP MicroProfile BOM BOM Artifact ID Use Case jboss-eap-xp-microprofile This BOM, whose groupId is org.jboss.bom , packages many JBoss EAP MicroProfile supported API dependencies, such as microprofile-openapi-api and microprofile-config-api . If you use this BOM, you need not specify a version for a supported API dependency, because the jboss-eap-xp-microprofile BOM specifies this value for the dependency. 4.1.4. Using the JBoss EAP MicroProfile Maven repository You can access the jboss-eap-xp-microprofile BOM after you install the Red Hat JBoss Enterprise Application Platform 7.4.0.GA Maven repository and apply the JBoss EAP XP Incremental Maven repository to it. The repository name then becomes JBoss EAP 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 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. Warning Configuring the JBoss EAP 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.4 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 MicroProfile Maven repository. After you have configured the JBoss EAP 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 the pom.xml file: <dependencyManagement> <dependencies> ... <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>4.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> ... </dependencies> </dependencyManagement> Note If you do not specify a value for the type element in the pom.xml file, Maven specifies a jar 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. MicroProfile Config development 4.2.1. Creating a Maven project for MicroProfile Config Create a Maven project with the required dependencies and the directory structure for creating an MicroProfile Config application. Prerequisites Maven is installed. Procedure Set up the Maven project. This creates the directory structure for the project and pom.xml configuration file. To let the POM file automatically manage the versions for the MicroProfile Config artifact and the 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>4.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> Add the MicroProfile Config artifact and the 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 MicroProfile Config and the 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. Set provided 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. Set provided 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. Set provided 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 MicroProfile Config is enabled in JBoss EAP. The latest POM is installed. The Maven project is configured for creating an MicroProfile Config application. Procedure Create the directory to store class files: Where APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Navigate to the new directory: 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 Jakarta RESTful Web Services 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 no ConfigSource is configured, the value jim is returned. Create an empty file named beans.xml in the src/main/webapp/WEB-INF/ directory: Where APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Navigate to the root directory of the application: Where APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Build the project: Test the output: The following is the expected output: 4.3. 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: Enable the microprofile-fault-tolerance-smallrye subsystem using the following managenent command: Reload the server with the following management command: 4.3.2. Configuring Maven project for MicroProfile Fault Tolerance Create a Maven project with the required dependencies and the directory structure for creating an 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 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>USD{version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> Replace USD{version.microprofile.bom} with the installed version of BOM. Add the MicroProfile Fault Tolerance artifact, managed by the BOM, to the <dependency> section of the project POM file. The following example demonstrates adding the 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: APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Navigate to the new directory: 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 Jakarta Contexts and Dependency Injection 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)); } } 1 Define number of re-tries to 4 . 2 Define the timeout interval in milliseconds. 3 Define a fallback method to call when invocation fails. Navigate to the root directory of the application: Build the application using the following Maven command: 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. MicroProfile Health development 4.4.1. The 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 might be included. Any Jakarta Contexts and Dependency Injection beans that include the org.eclipse.microprofile.health.Liveness , org.eclipse.microprofile.health.Readiness , or org.eclipse.microprofile.health.Startup annotations 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.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Liveness; import javax.enterprise.context.ApplicationScoped; @Liveness @ApplicationScoped public class HealthTest implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("health-test").up().build(); } } After you deploy a health check, any subsequent health check queries include the custom checks, as demonstrated in the following example. Note You can use the following commands for liveness, readiness, and startup checks: /subsystem=microprofile-health-smallrye:check-live /subsystem=microprofile-health-smallrye:check-ready /subsystem=microprofile-health-smallrye:check-started 4.4.2. The @Liveness annotation example The following example demonstrates how to use 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 how to check the connection to a database. If the database is down, the readiness check reports an 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.4.4. The @Startup annotation example The following is an example of using the @Startup annotation in an application. @Startup @ApplicationScoped public class StartupHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.up("Application started"); } } 4.5. MicroProfile JWT application development 4.5.1. Enabling microprofile-jwt-smallrye subsystem The 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: Enable the microprofile-jwt-smallrye subsystem: Reload the server: 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: 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 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>USD{version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> Replace USD{version.microprofile.bom} with the installed version of BOM. Add the MicroProfile JWT artifact, managed by the BOM, to the <dependency> section of the project POM file. The following example demonstrates adding the 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 MicroProfile JWT Create an application that authenticates requests based on JWT tokens and implements authorization based on the identity of the token bearer. Note The following procedure provides example code for generating tokens. For a production environment, use an identity provider such as Red Hat single sign-on (SSO). Prerequisites Maven project is configured with the correct dependencies. Procedure Create a token generator. This step serves as a reference. For a production environment, use an identity provider such as Red Hat SSO. Create a directory src/test/java for token the generator utility and navigate to it: 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 the src/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 = birthdate.withYear(today.getYear()); if (today.equals()) { return "Happy Birthday"; } if (.isBefore(today)) { = .withYear(.getYear() + 1); } Period wait = today.until(); return String.format("%d months and %d days until your birthday.", wait.getMonths(), wait.getDays()); } return "Sorry, we don't know your birthdate."; } } The methods annotated with @Path are the Jakarta RESTful Web Services endpoints. The annotation @Claim defines a JWT claim. Create a class file App.java to enable Jakarta RESTful Web Services: 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: Generate JWT token using the token generator utility: Build and deploy the application using the following Maven command: Test the application. Call the Sample/subscription endpoint using the bearer token: Call the Sample/birthday endpoint: 4.6. MicroProfile Metrics development 4.6.1. Creating an 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: Test the metrics: Issue the following command in the CLI: Expected output: In a browser, navigate to the URL http://localhost:8080/helloworld-rs/rest/json. Re-Issue the following command in the CLI: Expected output: 4.7. Developing an MicroProfile OpenAPI application 4.7.1. Enabling 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 MicroProfile subsystems and extensions to update the standalone.xml configuration file. Procedure Enable the MicroProfile OpenAPI smallrye extension in JBoss EAP: Enable the microprofile-openapi-smallrye subsystem using the following management command: Reload the server. The microprofile-openapi-smallrye subsystem is enabled. 4.7.2. Configuring Maven project for MicroProfile OpenAPI Create a Maven project to set up the dependencies for creating an MicroProfile OpenAPI application. Prerequisites Maven is installed. JBoss EAP Maven repository is configured. Procedure Initialize the project: 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>4.0.0.GA</version.server.bom> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>USD{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>USD{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 MicroProfile OpenAPI application Create an application that returns an OpenAPI v3 document. Prerequisites Maven project is configured for creating an MicroProfile OpenAPI application. Procedure Create the directory to store class files: APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Navigate to the new directory: 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: Build and deploy the application using the following Maven command: Test the application. Access the OpenAPI documentation of the sample application using curl : 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: USDref: '#/components/schemas/Fruit' post: requestBody: content: application/json: schema: USDref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: USDref: '#/components/schemas/Fruit' delete: requestBody: content: application/json: schema: USDref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: USDref: '#/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 Jakarta RESTful Web Services 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: APPLICATION_ROOT is the directory containing the pom.xml configuration file for the application. Query the OpenAPI endpoint, redirecting the output to a file: 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: Rebuild the application: Deploy the application again using the following management CLI commands: Undeploy the application: Deploy the application: JBoss EAP now serves a static OpenAPI document at the OpenAPI endpoint. 4.8. MicroProfile REST Client development 4.8.1. A comparison of MicroProfile REST client and Jakarta RESTful Web Services 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 the use of the Jakarta RESTful Web Services-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}") @Consumes("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: 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 @HeaderParam : @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. 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 Jakarta RESTful Web Services. 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.6. Context dependency injection with MicroProfile REST client With the MicroProfile REST client, you must annotate any interface that is managed as a Jakarta contexts and dependency injection (Jakarta Contexts and Dependency Injection) 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 use MicroProfile configuration to supply the information externally: <fully qualified name of TestDataBase>/mp-rest/url=<URL> For example, the following property 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 You can supply a number of other properties to Jakarta Contexts and Dependency Injection clients. For example, com.mycompany.remoteServices.MyServiceClient/mp-rest/providers , comma-separated list of fully-qualified provider class names to include in the client. Additional resources For more information about the MicroProfile REST Client specification, see Rest Client for MicroProfile . For more information about MicroProfile REST Client 2.0 features, see MicroProfile REST Client 2.0 .

Commands:
unzip -o jboss-eap-xp-4.0.0-incremental-maven-repository.zip -d EAP_MAVEN_REPOSITORY_PATH
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>4.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
mvn archetype:generate -DgroupId=com.example -DartifactId=microprofile-config -DinteractiveMode=false -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp cd microprofile-config
<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>4.0.0.GA</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<!-- 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. Set provided 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. Set provided 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. Set provided 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>
mkdir -p APPLICATION_ROOT /src/main/java/com/example/microprofile/config/
cd APPLICATION_ROOT /src/main/java/com/example/microprofile/config/
package com.example.microprofile.config; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/") public class HelloApplication extends Application { }
package com.example.microprofile.config; public class HelloService { String createHelloMessage(String name){ return "Hello " + name; } }
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 + "\"}"; } }
touch APPLICATION_ROOT /src/main/webapp/WEB-INF/beans.xml
cd APPLICATION_ROOT
mvn clean install wildfly:deploy
curl http://localhost:8080/microprofile-config/config/json
result: Hello jim
/extension=org.wildfly.extension.microprofile.fault-tolerance-smallrye:add
/subsystem=microprofile-fault-tolerance-smallrye:add
reload
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
<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>USD{version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<!-- 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>
mkdir -p APPLICATION_ROOT /src/main/java/com/example/microprofile/faulttolerance
cd APPLICATION_ROOT /src/main/java/com/example/microprofile/faulttolerance
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; } }
package com.example.microprofile.faulttolerance; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/") public class CoffeeApplication extends Application { }
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()); } }
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)); } }
cd APPLICATION_ROOT
mvn clean install wildfly:deploy
import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Liveness; import javax.enterprise.context.ApplicationScoped; @Liveness @ApplicationScoped public class HealthTest implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("health-test").up().build(); } }
[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:check { "outcome" => "success", "result" => { "status" => "UP", "checks" => [ { "name" => "deployments-status", "status" => "UP", "data" => {" <deployment_name> .war" => "OK"} }, { "name" => "server-state", "status" => "UP", "data" => {"value" => "running"} }, { "name" => "boot-errors", "status" => "UP" }, { "name" => "health-test", "status" => "UP" }, { "name" => "ready-deployment. <deployment_name> .war, "status" => "UP" }, { "name" => "started-deployment. <deployment_name> .war", "status" => "UP" } ] } }
@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(); } }
@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"); } } }
@Startup @ApplicationScoped public class StartupHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.up("Application started"); } }
/extension=org.wildfly.extension.microprofile.jwt-smallrye:add
/subsystem=microprofile-jwt-smallrye:add
reload
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
<dependencyManagement> <dependencies> <!-- importing the microprofile BOM adds MicroProfile specs --> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>USD{version.microprofile.bom}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<!-- 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>
mkdir -p src/test/java cd src/test/java
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)); } }
<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>
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."; } }
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 {}
mvn package
mvn exec:java -Dexec.mainClass=org.wildfly.quickstarts.mpjwt.TokenUtil -Dexec.classpathScope=test -Dexec.args="testUser 2017-09-15 Echoer Subscriber"
mvn package wildfly:deploy
curl -H "Authorization: Bearer ey..rg" http://localhost:8080/microprofile-jwt/rest/Sample/subscription
curl -H "Authorization: Bearer ey..rg" http://localhost:8080/microprofile-jwt/rest/Sample/birthday
package com.example.microprofile.metrics; public class HelloService { String createHelloMessage(String name){ return "Hello" + name; } }
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") + "\"}"; } }
<dependency> <groupId>org.eclipse.microprofile.metrics</groupId> <artifactId>microprofile-metrics-api</artifactId> <scope>provided</scope> </dependency>
mvn clean install wildfly:deploy
curl -v http://localhost:9990/metrics | grep request_count | grep helloworld-rs-metrics
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
curl -v http://localhost:9990/metrics | grep request_count | grep helloworld-rs-metrics
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
/extension=org.wildfly.extension.microprofile.openapi-smallrye:add()
/subsystem=microprofile-openapi-smallrye:add()
reload
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
<?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>4.0.0.GA</version.server.bom> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.bom</groupId> <artifactId>jboss-eap-xp-microprofile</artifactId> <version>USD{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>USD{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>
mkdir -p APPLICATION_ROOT /src/main/java/com/example/microprofile/openapi/
cd APPLICATION_ROOT /src/main/java/com/example/microprofile/openapi/
package com.example.microprofile.openapi; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/inventory") public class InventoryApplication extends Application { }
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; } }
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; } }
cd APPLICATION_ROOT
mvn wildfly:deploy
curl http://localhost:8080/openapi
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: USDref: '#/components/schemas/Fruit' post: requestBody: content: application/json: schema: USDref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: USDref: '#/components/schemas/Fruit' delete: requestBody: content: application/json: schema: USDref: '#/components/schemas/Fruit' responses: "200": description: OK content: application/json: schema: type: array items: USDref: '#/components/schemas/Fruit' components: schemas: Fruit: type: object properties: description: type: string name: type: string
mkdir APPLICATION_ROOT /src/main/webapp/META-INF
curl http://localhost:8080/openapi?format=JSON > src/main/webapp/META-INF/openapi.json
echo "mp.openapi.scan.disable=true" > APPLICATION_ROOT /src/main/webapp/META-INF/microprofile-config.properties
mvn clean install
undeploy microprofile-openapi.war
deploy APPLICATION_ROOT /target/microprofile-openapi.war
@Path("resource") public class TestResource { @Path("test") @GET String test() { return "test"; } }
Client client = ClientBuilder.newClient(); String response = client.target("http://localhost:8081/test").request().get(String.class);
@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();
@Path("resource") 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); }
POST /resource/test/p/?query=q HTTP/1.1 Accept: text/html Content-Type: text/plain Content-Length: 1 e
TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .register(MyClientResponseFilter.class) .register(MyMessageBodyReader.class) .build(TestResourceIntf.class);
@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); }
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) String contentLang(@HeaderParam(HttpHeaders.CONTENT_LANGUAGE) String contentLanguage, String subject);
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) @ClientHeaderParam(name=HttpHeaders.CONTENT_LANGUAGE, value="{getLanguage}") String contentLang(String subject); default String getLanguage() { return ...; }
@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); }
@Path("database") @RegisterRestClient(baseUri="https://localhost:8080/webapp") public interface TestDataBase { @Path("") @POST public String getByName(String name); }
<fully qualified name of TestDataBase>/mp-rest/url=<URL>
com.bluemonkeydiamond.TestDatabase/mp-rest/url=https://localhost:8080/webapp