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.
Jakarte-EE-8
MicroProfile 4.1
Jakarta-MVC-1.1.0
(JSF as template engine)
The project uses the following client side libraries in form os webjars.
MDB-3.9.0
(Based on Bootstrap 5.Final)Fontawesome-5.15.2
System Requirements
This project depends on the following software:
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:
@RequestScoped
because in MVC we are stateless@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:
- The http method used e.g.
@GET get()
- The action name e.g.
@Path("/action") action()
The endpoints either return
String
if a view is returned,- or
Response
when no view is needed
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.
/context
The directory which encapsulates the resources and views of a context which could be for instancebasic
oradvanced
./index.xhtml
The index page for a context which is the entry point and contains the links to the several views./example-one.xhtml
The view for the example one.
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.
/<framework>-<version>
The directory for a framework such asmdb
which also defines the version.
If a webjar of the used framework exists, then the webjar should be used./css
The directory for all the applications stylings./img
The directory for all the applications images./<context>
The directory holding the images for a context/<view>
The directory holding the images for a context view
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.
/
The directory for commonly used javascript modules, which can be used in any page and other javascript modules./tags
The directory for javascript modules used by JSF tags./basic/<mp_spec>
The directory for all basic examples javascript modules of a specification./advanced/<mp_spec>
The directory for all advanced examples javascript modules of a specification.
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.
- Username:
admin
- Password:
admin@123
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:
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
client-id=training
The client id of the training application.auth-baseUrl=http://localhost:18080/auth/
The base url where the client can reach Keycloak.
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:
id
The page unique idtext
The text for the buttonpath
(Optional, Default=’#’)
The relative path from the rest application path on, or a in-page references via ‘#’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"/>
Link
Renders an ordinary link with a text for executing GET requests.
The component accepts children, for instance an icon.
Attributes:
id
The page unique idtext
(Optional)
The text for the linkpath
(Optional, Default=’#’)
The relative path from the rest application path on, or a in-page references via ‘#’.target
(Optional, Default=’_self’)
The link targetrendered
(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>
NavLink
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:
id
The page unique idpath
The relative path from the rest application path ontext
(Optional) The text for the linkrendered
(Optional, Default=true)
The rendered flagactive
(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:
id
The unique card id within a viewtitle
The card titlerendered
(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:
id
The page unique idtitle
(Optional) The title of the timer dialogtext
(Optional) The text of the timer dialogclosable
(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:
id
The page unique idtitle
(Optional) The title of the timer dialogtext
(Optional) The text of the timer dialogclosable
(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.