Skip to the content.

Welcome to the microprofile-training Wiki!

The intention of this repository is to provide a training for the microprofile specifications in conjunction with the Jakarta Enterprise platform. This project will be developed under the umbrella of a Gepardec Learning Friday Project.

Developer Guide

This page represents the guide which will help you setup the project on your local machine and tells you how to contribute to the project.

The project is based on the following listed specifications.

The project uses the following client side libraries in form os webjars.

System Requirements

This project depends on the following software:

  1. OpenJDK-17
  2. Wildfly-26.1.3.Final
  3. Maven-3.9.x
  4. Docker
  5. Docker Compose

How to set up your environment

Install the depending software, check out the github repository and setup the IDE of your choice. See the repository root README for further details.

How to provide documentation

All of our documentation is available as Github Pages, which relates to the projects repository.

How to use Git Branching

We use the simple Github-Flow because we need no special version handling. We name our feature branches feature/<ticket_id_or_expresive_name> and merge them via reviewed Merge-Requests.

How to implement (MVC) Controllers

Controllers are JAX-RS endpoint classes with the naming scheme *Controller e.g. MPConfigController which provide http based action endpoints and for views to render.

All controller classes need to be annotated with:

  1. @RequestScoped because in MVC we are stateless
  2. @Controller which enables the MVC framework for all contained methods

If not all contained endpoints return a view, then only those endpoint methods need to be annotated with @Controller which return a view.

The http endpoint are named either as:

  1. The http method used e.g. @GET get()
  2. The action name e.g. @Path("/action") action()

The endpoints either return

See the following snippet for an example controller implementation.

import javax.ws.rs.core.Response;

@Path("/path")
@RequestScoped
public class MyController {

    @Path("/")
    @GET
    @Controller
    public String get() {
        return "path/index.xhtml";
    }

    @Path("/action")
    @GET
    @Controller
    public String action() {
        // Do something and return the page
        return get();
    }

    @Path("/post")
    @POST
    public Response action() {
        // Do something and return the response
        return Response.ok().build();
    }
}

For further learning about Jakarta-MVC controller see the Jakarta MVC 1.1.0 specification.

How to implement (MVC) JSF views

The template engine we have chosen is JSF where we only use the templating part of JSF to provide us with common components, so that we have consistent views and an easy way to implement views. The rest is actually plain html, css, text and javascript.

The root directory for our views is src/main/webapp/WEB-INF/views where we organize our views with the following structure.

See section for a list of available JSF components for building GUIs.

How to provide static resources

In Jakarta-MVC static resources can be provided when located in the webapp directory. The static resources are referenced via normal links, no JSF involved.

The main thing to consider is that the @ApplicationPath(...) of the JAX-RS Application must not define the root path, because the rest engine cannot provide the static resources.

The static resource are organized in the root directory src/main/webapp/resources and with the following structure.

All provided images must be PNG files.

How to use javascript

Inline javascript must be avoided, and all javascript for a page is implemented in its own file as a javascript ES-6 module.

The root directory for all javascript files is src/main/webapp/resources/js where the javascript files are structured in subdirectories depending on the context they are used for.

Javascript modules are implement at least in the following way.

// Optional state object
const state = {
    attribute: value,
};

// Optional (e.g.: click event listener registration)
const registerElementClickEventListener = (options) => {
    const {
        element,
    } = options;
    element.addEventListener('click', () => console.log('element clicked'));
};

const init = (options) => {
};

export default {
    init,
};

Parameters for the javascript module are always provided as an options object. If html elements are used in any ways, then the html element instance itself is provided and never ids.

Javascript modules are used in a xhtml page the following way.

<!-- Inserts the contained tags at the end of the HTML page -->
<ui:define name="bottom"> 
    <!-- The used javascript is a javascript module -->
    <script type="module">
        // '#{pathHelper.buildResourcePath("/js/mp.js")}' is a EL-Expression which gets resolved to ''/mptraining/resources/js/mp.js''
        // Imports your modules
        import mp from '#{pathHelper.buildResourcePath("/js/mp.js")}'
        import myModule from '#{pathHelper.buildResourcePath("/js/basic/<MP_SPEC>/myModule.js")}'

        // Initializes your javascript module once per page load
        mp.registerOnLoad(() => myModule.init({
            ...
        }));
    </script>
</ui:define>

Infrastructure guide

This is the infrastructure guide which helps you to set up and run the infrastructure and to provide modifications to the infrastructure definition.

We use docker-compose for the infrastructure which has a good support on all platforms.

All applications define the following admin user.

The following listed application are available.

Application URL Task
Keycloak http://localhost:18080/auth/ OpenId-Connect Server
Postgres jdbc:postgresql://localhost:15432/mptraining Database
Grafana http://localhost:13000 Metric Frontend
Prometheus http://localhost:19090 Metric Backend
JaegerUI http://localhost:16686/search Tracing Backend

Keycloak

Keycloak is used to secure the training application via MicroProfile JWT.

The resources of Keycloak are located at /infrastructure/keycloak and are:

  1. v<N>_realm-export.json
    The exported keycloak configuration.

! During the Export Keycloak replaces all secrets with *, which need to be added again.

! The Export is only partial. Make sure you add the training-users manually to the exported json.

Client Setup

JSF Components

Tags

The custom JSF tags implement GUI components which are used globally.

xmlns:tag="http://tags.microprofile.training.gepardec.com"

Button

Renders a link acting as a button, for executing GET requests like with a link.

Attributes:

  1. id
    The page unique id
  2. text
    The text for the button
  3. path (Optional, Default=’#’)
    The relative path from the rest application path on, or a in-page references via ‘#’
  4. rendered (Optional, Default=true)
    The rendered flag
<tag:button id="toMyActionOrResource" text="Go to example"/>
<tag:button id="toMyActionOrResource" text="Go to example" path="#otherId"/>
<tag:button id="toMyActionOrResource" text="Go to example" path="/basic/index"/>

Renders an ordinary link with a text for executing GET requests.
The component accepts children, for instance an icon.

Attributes:

  1. id
    The page unique id
  2. text (Optional)
    The text for the link
  3. path (Optional, Default=’#’)
    The relative path from the rest application path on, or a in-page references via ‘#’.
  4. target (Optional, Default=’_self’)
    The link target
  5. rendered (Optional, Default=true)
    The rendered flag
<tag:link id="toMyActionOrResource" text="Go to example"/>
<tag:link id="toMyActionOrResource" text="Go to example" path="#otherId"/>
<tag:link id="toMyActionOrResource" text="Go to example" path="/basic/index" target="_blank"/>
<tag:link id="toMyActionOrResource" text="Go to example" path="/basic/index" target="_blank">
    <i class="fas fa-arrow"/>
</tag:link>

Renders a nav link with a text for executing GET requests from the navbar.
The link is marked active if the user is on the current page or if the active flag ist set.
The component accepts children, for instance an icon.

Attributes:

  1. id
    The page unique id
  2. path
    The relative path from the rest application path on
  3. text (Optional) The text for the link
  4. rendered (Optional, Default=true)
    The rendered flag
  5. active (Optional, Default=false)
    The active flag marking the nav link active
<tag:navLink id="toMyResource" path="/basic/index" text="Go to example"/>
<tag:navLink id="toMyResource" text="Go to example" path="/basic/index" active="#{pathHelper.isOnSubpage('/basic/config')}">
    <i class="fas fa-arrow"/>
</tag:navLink>

Card

Renders a card with a header and button section used for an element in teh index pages.

Attributes:

  1. id
    The unique card id within a view
  2. title
    The card title
  3. rendered (Optional, default true)
    The rendered flag
<tag:card id="indexExampleMpHealth" title="First part">
    <ui:define name="body">
        <p>...</p>
    </ui:define>
    <ui:define name="buttons">
        <tag:button id="toMpHealthExample" text="Go to example" path="/basic/health"/>
    </ui:define>
</tag:card>

ModalDialog

Renders a modal dialog which displays the content provided.
This component is useful when one wants to display a message to the user.

Attributes:

  1. id
    The page unique id
  2. title (Optional) The title of the timer dialog
  3. text (Optional) The text of the timer dialog
  4. closable (Optional) The flag indicating the user can close the dialog
<tag:modalDialog id="message" />
<tag:modalDialog id="message" title="Information" />
<tag:modalDialog id="message" title="Information" text="Informative message"/>

The timer dialog can be controlled in Javascript via the Javascript Component where the html element to provide has the id you provided for the tag.

TimerDialog

Renders a timer modal dialog which displays a spinner and the duration seconds the dialog is displayed.
This component is useful when one wants to display the duration of a rest-api call.
This component is based on the Modal Dialog Component.

Attributes:

  1. id
    The page unique id
  2. title (Optional) The title of the timer dialog
  3. text (Optional) The text of the timer dialog
  4. closable (Optional) The flag indicating the user can close the dialog
<tag:timerDialog id="timer" />
<tag:timerDialog id="timer" title="Waiting for response..." />
<tag:timerDialog id="timer" title="Waiting for response..." text="This should not take longer than 1 second"/>

The timer dialog can be controlled in Javascript via the Javascript Component where the html element to provide has the id you provided for the tag.

Login

Renders a span with the current logged in user and a button for logout. Automatically checks for availability of a token on load and if not present redirects to keycloak.

<tag:login />

Javascript Components

Modules

There are several global Javascript modules which are used by developers in page related Javascript modules and/or in JSF pages.

The global javascript modules are located at the root level of src/main/webapp/resources/js/ and are imported in the page related javascript modules via a relative reference.

import globalModule from '../../globalModule.js'

mp

A simple utility module.

Functions

registerOnLoad
registers a function to the DOMContentLoaded event which guarantees that the page has been fully loaded and that the DOM is ready.

mp.registerOnLoad((event) => myModule.init({ ... }));

registerClickListenerPreventDefault
registers a function to the click event on a DOM element and prevents the default event invocation.

mp.registerClickListenerPreventDefault(myElement, (event) => onClick());

httpClient

A simple http client wrapping the Javascript fetch api.

Take a look at MDN Promise to learn how to work with promises.

Take a look at MDN Fetch-API to see how to work with the response which itself is again a promise.

Functions

post
executes a post request and returns a promise with the return value of the provided callback function. If no callback is defined, then the fetch-api response is the return value.

httpClient.post({
    uri: 'http://localhost:8080/api/endpoint',
    successCallback: (response) => extractData(response),
    errorCallback: (response) => extractData(response),
}).then((data) => displayData(data))
    .catch((error) => console.log('A network eror occurred'))
    .finally(() => console.log("Call is finished"));        

postBatch
executes parallel post request and returns a promise array with the return value of the provided callback function. If no callback is defined, then the fetch-api response is the return value.

Be aware that there is a limitation of how many parallel request a browser can send with HTTP/1 protocol. Read the doc MDN Domain Sharding for more information.

httpClient.postBatch({
    uri: 'http://localhost:8080/api/endpoint',
    count: 2,
    successCallback: (response, callCount) => extractData(response, callCount),
    errorCallback: (response, callCount) => extractData(response, callCount),
}).then((data) => displayData(data))
    .catch((error) => displayError(error))
    .finally(() => console.log("Call is finished"));                               

get executes a get request and returns a promise with the return value of the provided callback function. If no callback is defined, then the fetch-api response is the return value.

httpClient.get({
    uri: 'http://localhost:8080/api/endpoint',
    successCallback: (response) => extractData(response),
    errorCallback: (response) => extractData(response),
}).then((data) => displayData(data))
    .catch((error) => console.log('A network eror occurred'))
    .finally(() => console.log("Call is finished"));        

timer

The timer module is used for invoking a function continuously with a specified delay and provides control functions for starting stopping the timer.

Functions

create
creates a timer instance initialized with the options.

const timer = timer.create({
    delayMillis: 5000,
    runFunction: runFunc,
    runData: runDataObj,
    startCallback: beforeStart,
    stopCallback: afterStop,
});

Classes

Timer

The Timer which handles the intervals and provides functions for controlling the underlying interval.

Functions

start
invokes the options.startCallback function and starts the timer only if not already started. The options.runData is set as the input parameter for the provided options.runFunction.

timer.start();

stop
stops the timer and invokes the options.stopCallback function only if the timer is running.

timer.stop();

restart
invokes the stop and then the start function.

timer.restart();

isRunning
Answers the question if the timer is running.

timer.isRunning();

modalDialog

A wrapper for interacting with an MDB modal dialog.
If the Javascript object for the dialog doesn’t exist for the provided html element, then it is created, otherwise the existing Javascript object is used.

Functions

init
registers the show and hide callbacks on the html element if provided.

modalDialog.init({
    element: element,
    showCallback: onShow,
    hideCallback: onHide,
})

create
calls init for the callback registration and creates a ModalDialog instance.

const dialog = modalDialog.init({
    element: element,
    showCallback: onShow,
    hideCallback: onHide,
})

Classes

ModalDialog

A wrapper for the MDB modal dialog for interacting with the underlying dialog.

Functions

show
shows the modal dialog.

dialog.show();

hide
hide the modal dialog.

dialog.hide();

inputNumber

InputNumber is a wrapper for the MDB input number which clears an invalid content onblur and sets the defined min attribute if defined.

Functions

init
register an onblur event listener creates the MDB input instance if not already instantiated.

inputNumber.init(outlineDiv);

update
updates the MDB input for recalculation of the outline label if MDB input is available.

Usage:

inputNumber.update(outlineDiv);

login

Functions

init
checks if the use has a valid token and redirects to keycloak otherwise. Updates the upnElement with the logged in users upn.

token returns the access_token from keycloak.