diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..0886c428 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "ghcr.io/devcontainers-extra/features/node-asdf:0": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "asciidoctor.asciidoctor-vscode", + "microprofile-community.vscode-microprofile-pack", + "vscjava.vscode-java-pack", + "Open-Liberty.liberty-tools-vscode" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/antora.yml b/.github/workflows/antora.yml new file mode 100644 index 00000000..2802f0e6 --- /dev/null +++ b/.github/workflows/antora.yml @@ -0,0 +1,31 @@ +name: Generate MicroProfile Tutorial + +on: + push: + branches: + - main # Or your default branch + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' # Use the Node.js version that matches your development environment + + - name: Install Antora + run: npm install -g @antora/cli @antora/site-generator-default + + - name: Generate Site with Antora + run: antora --fetch playbook.yml + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./build/site # The default output directory for Antora + publish_branch: gh-pages # The target branch for GitHub Pages diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..096746c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/README.adoc b/README.adoc index 449c7f4d..e41e8bf8 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,745 @@ -image:https://badges.gitter.im/eclipse/microprofile-samples.svg[link="https://app.gitter.im/#/room/#eclipse/microprofile-tutorial"] += MicroProfile Tutorial Documentation +:toc: macro +:toc-title: Table of Contents +:toclevels: 3 +:doctype: book -# MicroProfile API Tutorial Content +The MicroProfile Tutorial is a comprehensive guide for learning about Eclipse MicroProfile. This repository contains the documentation built with Antora. -This repo contains the source files that are used to build the _MicroProfile Tutorial_. The source files are authored in link:https://asciidoc.org/[AsciiDoc]. AsciiDoc is similar to markdown but is particularly suited for user documentation. The source files are processed and integrated into the MicroProfile Tutorial site using link:https://antora.org/[Antora], which is a tool for building documentation sites. +toc::[] + +# Getting Started with MicroProfile Tutorial + +This repository contains the documentation for the MicroProfile Tutorial, built with Antora. This README provides information on how to set up, build, and customize the documentation site. + +## Prerequisites + +### Install Antora +You need to install Antora using npm. If you don't have npm installed, you can install it by following the instructions at https://docs.npmjs.com/getting-started/installing-node. + +``` +npm install -g @antora/cli @antora/site-generator-default +``` + +### Recommended VS Code Extensions + +For the best experience editing AsciiDoc files in Visual Studio Code, we recommend installing the following extensions: + +* AsciiDoc extension by Asciidoctor (`asciidoctor.asciidoctor-vscode`) - Provides rich language support for AsciiDoc +* Auto Open Preview Panel (`matt-rudge.auto-open-preview-panel`) - Opens preview automatically when opening AsciiDoc files + +## Building the Documentation Site + +You can build the site by running the following commands in the root directory of the project: + +``` +./update-repo-url.sh +antora antora-assembler.yml +./fix-edit-links.sh +``` + +The first command ensures that the repository URLs are up-to-date. The second command will generate the site in the `build/site` directory. The third command fixes the "Edit this Page" links to properly point to GitHub instead of local file paths. + +Alternatively, you can use the following one-liner: + +``` +./update-repo-url.sh && antora antora-assembler.yml && ./fix-edit-links.sh +``` + +After building, you can open the `build/site/index.html` file in your web browser to view the site. + +## Running the Site Locally + +You can run the site locally using Python's built-in HTTP server. First, navigate to the `build/site` directory: +``` +cd build/site +``` +Then, run the following command: +``` +python3 -m http.server 8080 +``` +This will start a local server on port 8080. You can then open your web browser and navigate to `http://:8080` to view the site. + +[TIP] +==== +If port 8080 is already in use, you can free it by finding and stopping the process using it. For example, on Linux or macOS: ++ +---- +lsof -i :8080 +kill +---- +Replace `` with the process ID shown in the output. +==== + +## Development Workflow + +To streamline the development process, a development server script is included that can automatically rebuild the documentation when files change. + +### Using the Development Server + +The `dev-server.sh` script provides three commands: + +1. **Build only**: ++ +---- +./dev-server.sh build +---- ++ +This builds the documentation site once. + +2. **Build and serve**: ++ +---- +./dev-server.sh serve +---- ++ +This builds the documentation site and starts a local HTTP server on port 8080. + +3. **Build, serve, and watch for changes**: ++ +---- +./dev-server.sh watch +---- ++ +This builds the documentation site, starts a local HTTP server, and automatically rebuilds the site when files change. + +### Requirements + +The development server requires: + +- Python 3 (for the HTTP server) +- inotify-tools (for file watching, will be automatically installed if missing) + +### Typical Development Workflow + +1. Start the development server in watch mode: `./dev-server.sh watch` +2. Open your browser at http://localhost:8080 +3. Edit AsciiDoc files in the `modules/ROOT/pages/` directory +4. Save your changes and see them automatically reflected in the browser + +## Understanding Antora Configuration + +Antora uses several configuration files to manage the documentation site. The main files are: + +- `antora-assembler.yml`: The primary configuration file that defines the site structure, UI bundle, and supplemental files. +- `antora.yml`: Component configuration for the MicroProfile Tutorial, including metadata and edit URL configuration. +- `supplemental-ui/`: Directory containing customizations like favicons and custom partials. + +### Site Structure Configuration + +The main configuration file for the site is `antora-assembler.yml`, which defines: + +- **Site metadata**: Title, URL, and starting page +- **Content sources**: Where to find documentation content +- **UI bundle**: The theme and UI components to use +- **Output**: Where to generate the site +- **Asciidoc attributes**: Global attributes for all pages + +Here's a breakdown of the key sections: + +#### Site Information +[source,yaml] +---- +site: + title: MicroProfile Tutorial + url: https://microprofile.io + start_page: microprofile-tutorial::index.adoc + keys: + show_edit_page_link: true +---- + +#### Content Sources +[source,yaml] +---- +content: + sources: + - url: . + start_path: . + branches: HEAD +---- + +#### UI Configuration +[source,yaml] +---- +ui: + bundle: + url: https://github.com/ttelang/microprofile-documentation-ui/releases/download/latest/ui-bundle.zip + snapshot: true + supplemental_files: ./supplemental-ui +---- + +### Component Configuration + +The `antora.yml` file defines the documentation component: + +[source,yaml] +---- +name: microprofile-tutorial +title: MicroProfile Tutorial +version: 6.1 +edit_url: https://github.com/ttelang/microprofile-tutorial/edit/patch-15/modules/ROOT/pages/{path} +asciidoc: + attributes: + source-language: asciidoc@ + table-caption: false + xrefstyle: full +nav: + - modules/ROOT/pages/nav.adoc +start_page: index.adoc +---- + +Key properties: +- **name**: The component name used in xrefs and URLs +- **title**: The human-readable title +- **version**: The component version (appears in URLs and version selector) +- **edit_url**: The URL template for "Edit this Page" links +- **nav**: The navigation file(s) for the component +- **start_page**: The default page when accessing the component root + +### Navigation Configuration + +The navigation is defined in `modules/ROOT/pages/nav.adoc` and uses AsciiDoc with special Antora directives: + +[source,asciidoc] +---- +* xref:index.adoc[Home] +* xref:chapter01/chapter01.adoc[Chapter 1: Introduction] +* Chapter 2: Getting Started +** xref:chapter02/chapter02-01.adoc[Section 2.1: Environment Setup] +---- + +The navigation structure directly affects the sidebar menu in the generated site. + +## Navigation and Site Structure + +The navigation system in Antora is a critical component that determines how users find and access content. Understanding how navigation works can help you create a better user experience. + +### Navigation Components + +The MicroProfile Tutorial site navigation consists of: + +1. **Main Navigation**: Defined in `modules/ROOT/pages/nav.adoc`, controls the sidebar navigation. +2. **Breadcrumbs**: Shows the current page's location in the content hierarchy. +3. **Previous/Next Links**: Helps users navigate sequentially through content. +4. **Table of Contents**: Generated from page headings, provides in-page navigation. + +### Customizing Navigation + +The navigation can be customized in several ways: + +#### Modifying the nav.adoc File + +The main navigation structure is defined in `modules/ROOT/pages/nav.adoc`: + +[source,asciidoc] +---- +* xref:index.adoc[Home] +* xref:chapter01/chapter01.adoc[Chapter 1: Introduction] +* Chapter 2: Getting Started +** xref:chapter02/chapter02-01.adoc[Section 2.1: Environment Setup] +** xref:chapter02/chapter02-02.adoc[Section 2.2: First Steps] +---- + +Navigation entries can be: +- Direct links to pages (with `xref:`) +- Unlinked category headers (without `xref:`) +- Nested to create hierarchical structures (using multiple `*` characters) + +#### Navigation UI Customization + +To customize the navigation appearance: + +1. **Style Overrides**: Add CSS for navigation elements in your supplemental UI files. +2. **Template Customization**: Override the navigation templates in the UI bundle. +3. **JavaScript Enhancements**: Add interactive features like search, filtering, or collapsible sections. + +Example CSS customization for navigation: + +[source,css] +---- +/* Customizing the sidebar navigation */ +.nav-menu { + background-color: #f5f5f5; +} + +.nav-item.is-current-page > .nav-link { + color: #0d5aa7; + font-weight: bold; +} +---- + +### Navigation Best Practices + +1. **Logical Organization**: Group related content together in the navigation. +2. **Consistent Naming**: Use consistent naming conventions for pages and sections. +3. **Limited Nesting**: Avoid deep nesting (more than 3 levels) to prevent navigation complexity. +4. **Descriptive Labels**: Use clear, descriptive labels for navigation items. +5. **Progressive Disclosure**: Organize content from basic to advanced topics. + +### Advanced Navigation Features + +The UI bundle can be customized to include advanced navigation features: + +1. **Search Integration**: Add search functionality to help users find content quickly. +2. **Version Selector**: Allow users to switch between different versions of the documentation. +3. **Component Selector**: If you have multiple components, provide a way to navigate between them. +4. **Tag-Based Navigation**: Group content by tags or categories for alternative navigation paths. + +## About the fix-edit-links.sh Script + +The `fix-edit-links.sh` script is a necessary post-processing step in the build process. Despite having `edit_url` properly configured in `antora.yml`, when building from a local repository (`url: .` in `antora-assembler.yml`), Antora generates edit links that point to local file paths instead of GitHub URLs. + +The script performs a simple text replacement in the generated HTML files, replacing local file paths with proper GitHub repository URLs. This ensures that the "Edit this Page" links work correctly for users viewing the documentation. + +If you update the repository URL or branch name, make sure to update the replacement URL in the `fix-edit-links.sh` script accordingly. The current implementation assumes the GitHub repository URL is `https://github.com/ttelang/microprofile-tutorial` and the branch is `patch-15`. + +## Automating Repository URL Configuration + +To simplify the management of repository URLs across configuration files, the `update-repo-url.sh` script is provided. This script: + +1. Automatically detects your Git repository URL and current branch +2. Updates the `edit_url` in `antora.yml` +3. Updates the repository information in `fix-edit-links.sh` + +### Using the URL Configuration Script + +Run the script before building the documentation: + +``` +./update-repo-url.sh +``` + +This ensures that both the Antora configuration and the fix-edit-links script use the same repository URL and branch, eliminating duplication and reducing the chance of errors. + +When you clone the repository or switch branches, run this script to update the configuration automatically. + +## Customizing the Documentation Site + +The MicroProfile Tutorial documentation site can be customized in various ways to enhance its appearance and functionality. Here are the key customization options: + +### Favicon Configuration + +Favicons are configured using the `supplemental-ui/partials/head-meta.hbs` file, which is included in the HTML `` section of each page. The current setup includes: + +- An SVG favicon (primary) +- A PNG favicon (fallback for browsers that don't support SVG) + +To change the favicons: + +1. Replace the files in `supplemental-ui/img/`: + - `favicon.svg` - Vector version of the favicon + - `favicon.png` - Bitmap version of the favicon +2. Make sure the references in `head-meta.hbs` match your file names + +### UI Customization + +The UI bundle is defined in `antora-assembler.yml` under the `ui.bundle.url` key. The current configuration uses a custom UI bundle from the `microprofile-documentation-ui` repository. + +To apply additional customizations: + +1. Add or modify files in the `supplemental-ui/` directory: + - `partials/` - Override specific UI components + - `css/` - Add custom CSS styles + - `js/` - Add custom JavaScript + +Common customizations include: + +- Header and footer modifications +- Custom CSS for branding +- Additional JavaScript functionality + +### Edit Page Links + +Edit page links are configured in two places: + +1. `antora.yml` - The `edit_url` property defines the base URL for edit links +2. `antora-assembler.yml` - The `site.keys.show_edit_page_link` property enables the display of edit links + +If you change the repository URL or branch, update both: +- The `edit_url` in `antora.yml` +- The replacement URL in `fix-edit-links.sh` + +### Redirection Configuration + +The project includes a `redirect.html` file that automatically redirects users from the root URL to the latest version of the documentation. This is particularly useful when hosting the documentation on GitHub Pages or other static hosting services. + +The redirect is configured to: +- Use relative URLs for maximum compatibility +- Fall back to JavaScript-based redirection if needed + +### Further Customization Resources + +For more advanced customization options, refer to the Antora documentation: + +- [Antora UI Documentation](https://docs.antora.org/antora-ui-default/) +- [Customizing the UI](https://docs.antora.org/antora/latest/playbook/ui-supplemental-files/) +- [Page Templates](https://docs.antora.org/antora/latest/templates/) + +## Contributing to the Documentation + +### Directory Structure + +The MicroProfile Tutorial follows the standard Antora directory structure: + +- `modules/ROOT/pages/` - Contains the main documentation content in AsciiDoc format +- `modules/ROOT/assets/images/` - Contains images used in the documentation +- `modules/ROOT/examples/` - Contains code examples (if applicable) + +### AsciiDoc Best Practices + +When contributing to the documentation, follow these AsciiDoc best practices: + +1. Use proper heading levels (start with `==` for top-level headings within a page) +2. Use cross-references to link between pages (`xref:page-id.adoc[]`) +3. Add proper metadata to each page: ++ +---- += Page Title +:page-id: unique-id +---- ++ +4. For code examples, use proper language specifiers: ++ +---- +[source,java] +---- +// Java code here +---- +---- + +### Adding New Content + +To add new content to the documentation: + +1. Create a new `.adoc` file in the appropriate directory under `modules/ROOT/pages/` +2. Add a reference to the new page in the navigation file (`modules/ROOT/pages/nav.adoc`) +3. Build the site to verify your changes + +### Handling Images + +When adding images to the documentation: + +1. Place image files in the `modules/ROOT/images/` directory +2. Reference images using the `image::` directive: ++ +---- +image::image-name.png[Alt text for the image, width=600] +---- + +## Troubleshooting + +### Common Build Issues + +1. **Missing dependencies** + ``` + npm install -g @antora/cli @antora/site-generator-default + ``` + +2. **Edit links not working** + - Make sure `fix-edit-links.sh` is executable: `chmod +x fix-edit-links.sh` + - Verify the GitHub repository URL and branch in both `antora.yml` and `fix-edit-links.sh` + +3. **Incorrect links in navigation** + - Check that page references in `nav.adoc` use proper xref syntax + - Verify that page IDs are unique across the documentation + +### Getting Help + +If you encounter issues not covered in this documentation, check the following resources: + +- [Antora Documentation](https://docs.antora.org/) +- [AsciiDoc Syntax Quick Reference](https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/) +- [MicroProfile Community](https://microprofile.io/community/) + +## Hosting and Deployment + +The MicroProfile Tutorial documentation site is designed to be deployed as static HTML, which can be hosted on various platforms. Here are some common hosting options and deployment methods: + +### GitHub Pages + +GitHub Pages is a simple and free hosting option for static sites: + +1. Build the site: `antora antora-assembler.yml && ./fix-edit-links.sh` +2. Copy the contents of the `build/site` directory to your GitHub Pages branch (typically `gh-pages`) +3. Configure GitHub repository settings to enable GitHub Pages + +The `redirect.html` file can be renamed to `index.html` at the repository root to automatically redirect visitors to the latest documentation version. + +### Static Web Hosting + +Any static web hosting service can host the documentation: + +1. Build the site: `antora antora-assembler.yml && ./fix-edit-links.sh` +2. Upload the contents of the `build/site` directory to your web server +3. Configure your web server to serve `index.html` as the default document + +### Continuous Integration + +To automate the build and deployment process, you can use GitHub Actions or another CI/CD system: + +1. Set up a workflow that triggers on pushes to the main branch +2. Configure the workflow to: + - Install Node.js and required dependencies + - Run the Antora build + - Execute the fix-edit-links script + - Deploy the generated site to your hosting platform + +Example GitHub Actions workflow file (`.github/workflows/deploy.yml`): + +[source,yaml] +---- +name: Deploy Documentation + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install dependencies + run: npm install -g @antora/cli @antora/site-generator-default + + - name: Build site + run: | + antora antora-assembler.yml + chmod +x ./fix-edit-links.sh + ./fix-edit-links.sh + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./build/site +---- + +### Custom Domain Configuration + +If you're hosting the documentation on a custom domain: + +1. Update the `site.url` property in `antora-assembler.yml` to match your domain +2. Configure your domain's DNS settings to point to your hosting provider +3. If using GitHub Pages, add a CNAME file to the `build/site` directory + +## Future Improvements + +Consider these potential improvements to the documentation site: + +1. **Multi-version documentation**: Configure Antora to build multiple versions of the documentation from different branches or tags +2. **Search integration**: Add a custom search engine like Algolia DocSearch +3. **Analytics**: Integrate web analytics to track usage patterns +4. **PDF generation**: Enhance the PDF export capabilities with custom styling +5. **Interactive examples**: Add interactive code examples using tools like Asciidoctor-Kroki for diagrams + +## Managing Dependencies and Updates + +### Node.js Dependencies + +The MicroProfile Tutorial documentation relies on Node.js packages, primarily Antora. These dependencies are listed in the `package.json` file. To update or manage these dependencies: + +1. **Update all dependencies**: ++ +---- +npm update +---- + +2. **Install a specific Antora version**: ++ +---- +npm install -g @antora/cli@3.1.0 @antora/site-generator-default@3.1.0 +---- + +### Understanding the UI Bundle + +The UI bundle is a critical component of an Antora site that controls the overall appearance, layout, and functionality of the documentation. It's defined in `antora-assembler.yml` under the `ui.bundle` section. + +#### Current UI Bundle Configuration + +The MicroProfile Tutorial uses a customized UI bundle from the `microprofile-documentation-ui` repository: + +[source,yaml] +---- +ui: + bundle: + url: https://github.com/ttelang/microprofile-documentation-ui/releases/download/latest/ui-bundle.zip + snapshot: true + supplemental_files: ./supplemental-ui +---- + +Key properties in this configuration: + +* `url`: Specifies the location of the UI bundle ZIP file +* `snapshot: true`: Tells Antora to fetch the bundle every time you build, ignoring any cached version +* `supplemental_files`: Points to a directory containing files that override or extend the UI bundle + +#### UI Bundle Components + +A standard Antora UI bundle includes: + +* **CSS styles**: Controls the visual appearance of the site +* **JavaScript files**: Provides interactive functionality +* **Handlebars templates**: Defines the HTML structure of pages +* **Fonts and images**: Basic visual assets +* **Layouts**: Page structure templates for different content types + +#### Customizing the UI Bundle + +There are three approaches to customizing the UI: + +1. **Using supplemental files** (current approach): + * Add files to the `supplemental-ui/` directory + * These files override or extend the UI bundle without modifying it + * Good for simple customizations like favicons, headers, footers, and CSS tweaks + +2. **Creating a custom UI bundle**: + * Fork the default Antora UI repository + * Make extensive customizations + * Build and host your custom bundle + * Reference your custom bundle URL in `antora-assembler.yml` + +3. **Using a community UI bundle**: + * Several alternative UI bundles are available in the Antora ecosystem + * Each offers different features, layouts, and styling options + +#### Updating the UI Bundle + +To update to a newer version of the UI bundle: + +1. Check for new releases of the UI bundle at the GitHub repository +2. Update the URL in `antora-assembler.yml`: ++ +[source,yaml] +---- +ui: + bundle: + url: https://github.com/ttelang/microprofile-documentation-ui/releases/download/latest/ui-bundle.zip + snapshot: true +---- + +#### Creating a Custom UI Bundle + +If you need extensive customization beyond what supplemental files allow: + +1. Fork the https://github.com/ttelang/microprofile-documentation-ui repository +2. Make your customizations following the project's README +3. Build the UI bundle using `gulp bundle` +4. Host the resulting ZIP file (e.g., on GitHub Releases) +5. Update the `url` in `antora-assembler.yml` to point to your custom bundle + +This approach provides the most flexibility but requires more maintenance. + +## Advanced UI Bundle Configuration + +The UI bundle configuration in Antora offers several advanced options that can enhance the documentation site's functionality and performance. + +### Bundle Cache Control + +You can control how Antora handles UI bundle caching: + +[source,yaml] +---- +ui: + bundle: + url: https://github.com/ttelang/microprofile-documentation-ui/releases/download/latest/ui-bundle.zip + snapshot: true # Always fetch the latest version + # snapshot: false # Use cached version when available +---- + +Setting `snapshot: false` can improve build performance by using a cached version of the bundle. + +### Custom UI Bundle Features + +Modern UI bundles for Antora can include advanced features: + +1. **Search Integration**: Many UI bundles include built-in search functionality: ++ +[source,yaml] +---- +ui: + bundle: + url: https://example.com/ui-bundle.zip + supplemental_files: ./supplemental-ui +search: + engine: lunr # Or another search engine + index_latest_only: true +---- + +2. **Multiple Output Formats**: Some UI bundles support different output formats: ++ +[source,yaml] +---- +ui: + bundle: + url: https://example.com/ui-bundle.zip +output: + dir: ./build/site + formats: + - html + - pdf +---- + +3. **SEO Optimizations**: Enhanced metadata for search engines: ++ +[source,yaml] +---- +site: + title: MicroProfile Tutorial + url: https://microprofile.io + keys: + seo_description: Comprehensive tutorial for learning Eclipse MicroProfile + seo_keywords: microprofile, java, cloud-native, microservices +---- + +### Using a Local UI Bundle + +For development or customization, you can use a local UI bundle: + +[source,yaml] +---- +ui: + bundle: + url: ./path/to/local/ui-bundle.zip + snapshot: true +---- + +This approach is useful when developing your own UI bundle or testing modifications. + +### UI Bundle Development + +If you decide to develop your own UI bundle: + +1. **Prerequisites**: Node.js and Gulp +2. **Project Structure**: + - `src/`: Source files (CSS, JavaScript, Handlebars templates) + - `gulp.d/`: Gulp tasks for building the bundle + - `preview-src/`: Sample content for testing +3. **Build Process**: + - Run `gulp bundle` to create the UI bundle + - Use `gulp preview` to test with sample content + +### Performance Optimization Tips + +To optimize the performance of your documentation site: + +1. **Minification**: Ensure CSS and JavaScript are minified +2. **Image Optimization**: Use optimized images and consider lazy loading +3. **Font Subsetting**: Only include the character sets you need +4. **Resource Caching**: Configure proper caching headers +5. **CDN Integration**: Consider hosting assets on a CDN + +### Debugging UI Issues + +When troubleshooting UI problems: + +1. Use browser developer tools to inspect elements and styles +2. Check the browser console for JavaScript errors +3. Verify that all required resources are loading correctly +4. Test with different browsers to identify browser-specific issues \ No newline at end of file diff --git a/antora-assembler.yml b/antora-assembler.yml new file mode 100644 index 00000000..5c7ecf66 --- /dev/null +++ b/antora-assembler.yml @@ -0,0 +1,46 @@ +site: + title: MicroProfile Tutorial + url: https://microprofile.io + start_page: microprofile-tutorial::index.adoc + keys: + show_edit_page_link: true + +content: + sources: + - url: . + start_path: . + branches: HEAD + +ui: + bundle: + url: https://github.com/microprofile/microprofile-tutorial-ui/releases/download/latest/ui-bundle.zip + snapshot: true + supplemental_files: ./supplemental-ui + +asciidoc: + attributes: + experimental: true + idprefix: '' + idseparator: '-' + page-pagination: true + allow-uri-read: '' + page-pdf-download-name: microprofile-tutorial.pdf + source-language: asciidoc@ + table-caption: false + xrefstyle: full + extensions: + - asciidoctor-plantuml + - asciidoctor-kroki + - '@asciidoctor/tabs' + +antora: + extensions: + - require: '@antora/lunr-extension' + index_latest_only: true + - require: '@antora/pdf-extension' + generate_index: true + index_filename: microprofile-tutorial + output_format: pdf + +output: + dir: ./build/site diff --git a/antora.yml b/antora.yml new file mode 100644 index 00000000..250d330e --- /dev/null +++ b/antora.yml @@ -0,0 +1,12 @@ +name: microprofile-tutorial +title: MicroProfile Tutorial +version: 6.1 +edit_url: https://github.com/ttelang/microprofile-tutorial/edit/patch-15/modules/ROOT/pages/{path} +asciidoc: + attributes: + source-language: asciidoc@ + table-caption: false + xrefstyle: full +nav: + - modules/ROOT/pages/nav.adoc +start_page: index.adoc diff --git a/build/assembler/microprofile-tutorial/6.1/_images/figure1-2.png b/build/assembler/microprofile-tutorial/6.1/_images/figure1-2.png new file mode 100644 index 00000000..a9d1a072 Binary files /dev/null and b/build/assembler/microprofile-tutorial/6.1/_images/figure1-2.png differ diff --git a/images/figure4-1.png b/build/assembler/microprofile-tutorial/6.1/_images/figure4-1.png similarity index 100% rename from images/figure4-1.png rename to build/assembler/microprofile-tutorial/6.1/_images/figure4-1.png diff --git a/images/figure8-1.png b/build/assembler/microprofile-tutorial/6.1/_images/figure8-1.png similarity index 100% rename from images/figure8-1.png rename to build/assembler/microprofile-tutorial/6.1/_images/figure8-1.png diff --git a/build/assembler/microprofile-tutorial/6.1/_images/figureFM-1.png b/build/assembler/microprofile-tutorial/6.1/_images/figureFM-1.png new file mode 100644 index 00000000..934b5a46 Binary files /dev/null and b/build/assembler/microprofile-tutorial/6.1/_images/figureFM-1.png differ diff --git a/build/assembler/microprofile-tutorial/6.1/microprofile-tutorial.pdf b/build/assembler/microprofile-tutorial/6.1/microprofile-tutorial.pdf new file mode 100644 index 00000000..43e6202c Binary files /dev/null and b/build/assembler/microprofile-tutorial/6.1/microprofile-tutorial.pdf differ diff --git a/build/site/404.html b/build/site/404.html new file mode 100644 index 00000000..3d406417 --- /dev/null +++ b/build/site/404.html @@ -0,0 +1,110 @@ + + + + + +Page Not Found :: MicroProfile Tutorial + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+
+

Page Not Found

+
+

The page you’re looking for does not exist. It may have been moved. You can return to the start page, or follow one of the links in the navigation to the left.

+
+
+

If you arrived on this page by clicking on a link, please notify the owner of the site that the link is broken. +If you typed the URL of this page manually, please double check that you entered the address correctly.

+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/_/css/search.css b/build/site/_/css/search.css new file mode 100644 index 00000000..c06262bd --- /dev/null +++ b/build/site/_/css/search.css @@ -0,0 +1,131 @@ +.search-result-dropdown-menu { + position: absolute; + z-index: 100; + display: block; + right: 0; + left: inherit; + top: 100%; + border-radius: 4px; + margin: 6px 0 0; + padding: 0; + text-align: left; + height: auto; + background: transparent; + border: none; + max-width: 600px; + min-width: 500px; + box-shadow: 0 1px 0 0 rgb(0 0 0 / 20%), 0 2px 3px 0 rgb(0 0 0 / 10%); +} + +@media screen and (max-width: 768px) { + .search-result-dropdown-menu { + min-width: calc(100vw - 3.75rem); + } +} + +.search-result-dataset { + position: relative; + border: 1px solid #d9d9d9; + background: #fff; + border-radius: 4px; + overflow: auto; + padding: 8px; + max-height: calc(100vh - 5.25rem); + line-height: 1.5; +} + +.search-result-item { + display: flex; + margin-top: 0.5rem; +} + +.search-result-component-header { + color: #1e1e1e; + border-bottom: 1px solid #ddd; + margin-left: 0.5em; + margin-right: 0.5em; + padding-top: 0.25em; + padding-bottom: 0.25em; +} + +.search-result-document-title { + width: 33%; + border-right: 1px solid #ddd; + color: #02060c; + font-weight: 500; + font-size: 0.8rem; + padding: 0.5rem 0.5rem 0.5rem 0; + text-align: right; + position: relative; + word-wrap: break-word; +} + +.search-result-document-hit { + flex: 1; + font-size: 0.75rem; + color: #63676d; +} + +.search-result-document-hit > a { + color: inherit; + display: block; + padding: 0.55rem 0.25rem 0.55rem 0.75rem; +} + +.search-result-document-hit > a:hover { + background-color: rgb(69 142 225 / 5%); +} + +.search-result-document-hit .search-result-highlight { + color: #174d8c; + background: rgb(143 187 237 / 10%); + padding: 0.1em 0.05em; + font-weight: 500; +} + +.search-result-document-hit .search-result-section-title { + color: #303030; + font-weight: 500; + font-size: 1.05em; + margin-bottom: 0.25em; +} + +.search-result-document-hit .search-result-keywords { + margin-top: 0.25em; +} + +.search-result-document-hit .search-result-keywords-field-label { + font-weight: bold; +} + +#search-input { + padding: 0.25em; +} + +#search-input:focus { + outline: none; +} + +#search-field { + display: flex; +} + +#search-field .filter { + background: #fff linear-gradient(180deg, #e1e1e1 0, #e1e1e1) no-repeat 0 / 1px 50%; + border: 1px solid #e1e1e1; + border-left: none; + border-radius: 0 0.1em 0.1em 0; + color: #5d5d5d; + cursor: pointer; + font-size: 0.875em; + display: flex; + align-items: center; + padding: 0 0.5rem; + white-space: nowrap; + overflow: hidden; +} + +#search-field.has-filter > input { + border-right: none; + border-radius: 0.1em 0 0 0.1em; +} diff --git a/build/site/_/css/site.css b/build/site/_/css/site.css new file mode 100644 index 00000000..313cb198 --- /dev/null +++ b/build/site/_/css/site.css @@ -0,0 +1,11 @@ +@import url("https://fonts.googleapis.com/css2?family=Dosis:wght@400;500;700&display=swap");@import url("https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap");@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:300;src:url(../font/open-sans-latin-300-normal.woff2) format("woff2"),url(../font/open-sans-latin-300-normal.woff) format("woff")}@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:400;src:url(../font/open-sans-latin-400-normal.woff2) format("woff2"),url(../font/open-sans-latin-400-normal.woff) format("woff")}@font-face{font-family:Open Sans;font-style:italic;font-display:swap;font-weight:400;src:url(../font/open-sans-latin-400-italic.woff2) format("woff2"),url(../font/open-sans-latin-400-italic.woff) format("woff")}@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:500;src:url(../font/open-sans-latin-500-normal.woff2) format("woff2"),url(../font/open-sans-latin-500-normal.woff) format("woff")}@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:600;src:url(../font/open-sans-latin-600-normal.woff2) format("woff2"),url(../font/open-sans-latin-600-normal.woff) format("woff")}@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:700;src:url(../font/open-sans-latin-700-normal.woff2) format("woff2"),url(../font/open-sans-latin-700-normal.woff) format("woff")}@font-face{font-family:Open Sans;font-style:normal;font-display:swap;font-weight:800;src:url(../font/open-sans-latin-800-normal.woff2) format("woff2"),url(../font/open-sans-latin-800-normal.woff) format("woff")}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:400;src:url(../font/roboto-mono-latin-400-normal.woff2) format("woff2"),url(../font/roboto-mono-latin-400-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:500;src:url(../font/roboto-mono-latin-500-normal.woff2) format("woff2"),url(../font/roboto-mono-latin-500-normal.woff) format("woff");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}:root{--color-white:#fff;--color-smoke-10:#fefefe;--color-smoke-30:#fafafa;--color-smoke-50:#f5f5f5;--color-smoke-70:#f0f0f0;--color-smoke-90:#e1e1e1;--color-gray-10:#c1c1c1;--color-gray-30:#9c9c9c;--color-gray-40:#8e8e8e;--color-gray-50:grey;--color-gray-70:#5d5d5d;--color-jet-20:#4a4a4a;--color-jet-30:#424242;--color-jet-50:#333;--color-jet-70:#222;--color-jet-80:#191919;--color-black:#000;--color-brand-blue:#1b208b;--color-brand-yellow:#fdb940;--color-brand-orange:#f98200;--color-brand-grey:#58595b;--color-brand-dark:#131660;--color-brand-white:#fff;--color-brand-black:#3d3d3d;--rem-base:18;--body-font-size:1.0625em;--body-font-size--desktop:1.125em;--body-font-size--print:0.9375em;--body-line-height:1.15;--body-font-color:var(--color-jet-70);--body-font-family:"Lato",sans-serif;--heading-font-family:"Dosis",sans-serif;--body-font-weight-bold:500;--monospace-font-family:"Roboto Mono",monospace;--monospace-font-weight-bold:500;--body-background:var(--color-white);--panel-background:var(--color-smoke-30);--panel-border-color:var(--color-smoke-90);--scrollbar-track-color:var(--color-smoke-30);--scrollbar-thumb-color:var(--color-gray-10);--scrollbar_hover-thumb-color:var(--color-gray-30);--navbar-background:var(--color-white);--navbar-font-color:var(--body-font-color);--navbar_hover-background:var(--color-brand-dark);--navbar-button-background:var(--color-brand-orange);--navbar-button-border-color:var(--color-brand-orange);--navbar-button-font-color:var(--color-brand-white);--navbar-menu-border-color:var(--panel-border-color);--navbar-menu-background:var(--color-white);--navbar-menu-font-color:var(--body-font-color);--navbar-menu_hover-background:var(--color-smoke-50);--navbar-switch-background:var(--color-brand-white);--navbar-switch-button-background:var(--color-brand-black);--navbar-switch-button_hover-background:var(--color-brand-grey);--nav-background:var(--panel-background);--nav-border-color:var(--color-gray-10);--nav-line-height:1.35;--nav-heading-font-color:var(--color-jet-30);--nav-muted-color:var(--color-gray-70);--nav-panel-divider-color:var(--color-smoke-90);--nav-secondary-background:var(--color-smoke-70);--toolbar-background:var(--color-brand-yellow);--toolbar-border-color:var(--color-white);--toolbar-font-color:var(--color-brand-white);--toolbar-muted-color:var(--color-gray-10);--page-version-menu-background:var(--toolbar-background);--page-version-menu-font-color:var(--toolbar-muted-color);--page-version-missing-font-color:var(--color-gray-40);--caution-color:#a0439c;--caution-on-color:var(--color-white);--important-color:#d32f2f;--important-on-color:var(--color-white);--note-color:#217ee7;--note-on-color:var(--color-white);--tip-color:#41af46;--tip-on-color:var(--color-white);--warning-color:#e18114;--warning-on-color:var(--color-white);--doc-font-color:var(--color-jet-50);--doc-font-size:inherit;--doc-font-size--desktop:calc(17/var(--rem-base)*1rem);--doc-line-height:1.6;--doc-margin:0 auto;--doc-margin--desktop:0 2rem;--heading-font-color:var(--color-jet-80);--heading-font-weight:normal;--alt-heading-font-weight:var(--body-font-weight-bold);--section-divider-color:var(--panel-border-color);--link-font-color:#1565c0;--link_hover-font-color:#104d92;--link_unresolved-font-color:var(--important-color);--abstract-background:var(--color-smoke-70);--abstract-font-color:var(--color-jet-20);--abstract-border-color:var(--panel-border-color);--admonition-background:var(--panel-background);--admonition-label-font-weight:var(--body-font-weight-bold);--caption-font-color:var(--color-gray-70);--caption-font-style:italic;--caption-font-weight:var(--body-font-weight-bold);--code-background:var(--panel-background);--code-font-color:var(--body-font-color);--example-background:var(--color-white);--example-border-color:var(--color-gray-70);--kbd-background:var(--panel-background);--kbd-border-color:var(--color-gray-10);--pre-background:var(--panel-background);--pre-border-color:var(--panel-border-color);--pre-annotation-font-color:var(--color-gray-50);--quote-background:var(--panel-background);--quote-border-color:var(--color-gray-70);--quote-font-color:var(--color-gray-70);--quote-attribution-font-color:var(--color-gray-40);--sidebar-background:var(--color-smoke-90);--table-border-color:var(--panel-border-color);--table-stripe-background:var(--panel-background);--table-footer-background:linear-gradient(180deg,var(--color-smoke-70) 0%,var(--color-white));--toc-font-color:var(--nav-muted-color);--toc-heading-font-color:var(--doc-font-color);--toc-border-color:var(--panel-border-color);--toc-line-height:1.2;--footer-line-height:var(--doc-line-height);--footer-background:var(--color-smoke-90);--footer-font-color:var(--color-gray-70);--footer-link-font-color:var(--color-jet-80);--navbar-height:calc(63/var(--rem-base)*1rem);--toolbar-height:calc(45/var(--rem-base)*1rem);--drawer-height:calc(45/var(--rem-base)*1rem);--body-top:var(--navbar-height);--body-min-height:calc(100vh - var(--body-top));--nav-height:calc(var(--body-min-height) - var(--toolbar-height));--nav-panel-menu-height:calc(100% - var(--toolbar-height));--nav-panel-explore-height:calc(50% + var(--drawer-height));--nav-width:calc(270/var(--rem-base)*1rem);--toc-top:calc(var(--body-top) + var(--toolbar-height));--toc-height:calc(100vh - var(--toc-top) - 2.5rem);--toc-width:calc(162/var(--rem-base)*1rem);--toc-width--widescreen:calc(216/var(--rem-base)*1rem);--doc-max-width:calc(720/var(--rem-base)*1rem);--doc-max-width--desktop:calc(828/var(--rem-base)*1rem);--z-index-nav:1;--z-index-toolbar:2;--z-index-page-version-menu:3;--z-index-navbar:4}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}html{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:1.0625em;font-size:var(--body-font-size);height:100%;scroll-behavior:smooth}@media screen and (min-width:1024px){html{font-size:1.125em;font-size:var(--body-font-size--desktop)}}body{background:#fff;background:var(--body-background);color:#222;color:var(--body-font-color);font-family:Lato,sans-serif;font-family:var(--body-font-family);line-height:1.15;line-height:var(--body-line-height);margin:0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere}a{text-decoration:none}a:hover{text-decoration:underline}a:active{background-color:none}code,kbd,pre{font-family:Roboto Mono,monospace;font-family:var(--monospace-font-family)}b,dt,strong,th{font-weight:500;font-weight:var(--body-font-weight-bold)}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}em em{font-style:normal}strong strong{font-weight:400}button{cursor:pointer;font-family:inherit;font-size:1em;line-height:1.15;line-height:var(--body-line-height);margin:0}button::-moz-focus-inner{border:none;padding:0}summary{cursor:pointer;-webkit-tap-highlight-color:transparent;outline:none}table{border-collapse:collapse;word-wrap:normal}object[type="image/svg+xml"]:not([width]){width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}::-webkit-input-placeholder{opacity:.5}::-moz-placeholder{opacity:.5}:-ms-input-placeholder{opacity:.5}::-ms-input-placeholder{opacity:.5}::placeholder{opacity:.5}@media (pointer:fine){@supports (scrollbar-width:thin){html{scrollbar-color:#c1c1c1 #fafafa;scrollbar-color:var(--scrollbar-thumb-color) var(--scrollbar-track-color)}body *{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent;scrollbar-color:var(--scrollbar-thumb-color) transparent}}html::-webkit-scrollbar{background-color:#fafafa;background-color:var(--scrollbar-track-color);height:12px;width:12px}body ::-webkit-scrollbar{height:6px;width:6px}::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:#c1c1c1;background-color:var(--scrollbar-thumb-color);border:3px solid transparent;border-radius:12px}body ::-webkit-scrollbar-thumb{border-width:1.75px;border-radius:6px}::-webkit-scrollbar-thumb:hover{background-color:#9c9c9c;background-color:var(--scrollbar_hover-thumb-color)}}@media screen and (min-width:1024px){.body{display:-webkit-box;display:-ms-flexbox;display:flex}}.nav-container{position:fixed;top:3.5rem;top:var(--navbar-height);left:0;width:100%;font-size:.94444rem;font-size:calc(17/var(--rem-base)*1rem);z-index:1;z-index:var(--z-index-nav);visibility:hidden}@media screen and (min-width:769px){.nav-container{width:15rem;width:var(--nav-width)}}@media screen and (min-width:1024px){.nav-container{font-size:.86111rem;font-size:calc(15.5/var(--rem-base)*1rem);-webkit-box-flex:0;-ms-flex:none;flex:none;position:static;top:0;visibility:visible}}.nav-container.is-active{visibility:visible}.nav{background:#fafafa;background:var(--nav-background);position:relative;height:calc(100vh - 6rem);height:var(--nav-height)}@media screen and (min-width:769px){.nav{-webkit-box-shadow:1px 0 3px #c1c1c1;box-shadow:1px 0 3px #c1c1c1;-webkit-box-shadow:1px 0 3px var(--nav-border-color);box-shadow:1px 0 3px var(--nav-border-color)}}@media screen and (min-width:1024px){.nav{top:6rem;top:calc(var(--navbar-height) + var(--toolbar-height));-webkit-box-shadow:none;box-shadow:none;position:sticky}}.nav a{color:inherit}.nav .panels{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:inherit}html.is-clipped--nav{overflow-y:hidden}.nav-panel-menu{overflow-y:scroll;-ms-scroll-chaining:none;overscroll-behavior:none;height:calc(100% - 2.5rem);height:var(--nav-panel-menu-height)}.nav-panel-menu:not(.is-active) .nav-menu{opacity:.75}.nav-panel-menu:not(.is-active)::after{content:"";background:rgba(0,0,0,.5);display:block;position:absolute;top:0;right:0;bottom:0;left:0}.nav-menu{min-height:100%;padding:.5rem .75rem;line-height:1.35;line-height:var(--nav-line-height);position:relative}.nav-menu h3.title{color:#424242;color:var(--nav-heading-font-color);font-size:inherit;font-weight:500;font-weight:var(--body-font-weight-bold);margin:0;padding:.25em 0 .125em}.nav-list{list-style:none;margin:0 0 0 .75rem;padding:0}.nav-menu>.nav-list+.nav-list{margin-top:.5rem}.nav-item{margin-top:.5em}.nav-item-toggle~.nav-list{padding-bottom:.125rem}.nav-item[data-depth="0"]>.nav-list:first-child{display:block;margin:0}.nav-item:not(.is-active)>.nav-list{display:none}.nav-item-toggle{background:transparent url(../img/caret.svg) no-repeat 50%/50%;border:none;outline:none;line-height:inherit;padding:0;position:absolute;height:1.35em;height:calc(var(--nav-line-height)*1em);width:1.35em;width:calc(var(--nav-line-height)*1em);margin-top:-.05em;margin-left:-1.35em;margin-left:calc(var(--nav-line-height)*-1em)}.nav-item.is-active>.nav-item-toggle{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.is-current-page>.nav-link,.is-current-page>.nav-text{font-weight:500;font-weight:var(--body-font-weight-bold)}.nav-panel-explore{background:#fafafa;background:var(--nav-background);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:absolute;top:0;right:0;bottom:0;left:0}.nav-panel-explore:not(:first-child){top:auto;max-height:calc(50% + 2.5rem);max-height:var(--nav-panel-explore-height)}.nav-panel-explore .context{font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);-ms-flex-negative:0;flex-shrink:0;color:#5d5d5d;color:var(--nav-muted-color);-webkit-box-shadow:0 -1px 0 #e1e1e1;box-shadow:0 -1px 0 #e1e1e1;-webkit-box-shadow:0 -1px 0 var(--nav-panel-divider-color);box-shadow:0 -1px 0 var(--nav-panel-divider-color);padding:0 .5rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;line-height:1;height:2.5rem;height:var(--drawer-height)}.nav-panel-explore:not(:first-child) .context{cursor:pointer}.nav-panel-explore .context .version{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:inherit;-ms-flex-align:inherit;align-items:inherit;white-space:nowrap}.nav-panel-explore .context .version::after{content:"";background:url(../img/chevron.svg) no-repeat 100%/auto 100%;width:1.25em;height:.75em}.nav-panel-explore .components{line-height:1.6;line-height:var(--doc-line-height);-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-shadow:inset 0 1px 5px #e1e1e1;box-shadow:inset 0 1px 5px #e1e1e1;-webkit-box-shadow:inset 0 1px 5px var(--nav-panel-divider-color);box-shadow:inset 0 1px 5px var(--nav-panel-divider-color);background:#f0f0f0;background:var(--nav-secondary-background);padding:.5rem .75rem 0;margin:0;overflow-y:scroll;-ms-scroll-chaining:none;overscroll-behavior:none;max-height:100%;display:block}.nav-panel-explore:not(.is-active) .components{display:none}.nav-panel-explore .component{display:block}.nav-panel-explore .component+.component{margin-top:.5rem}.nav-panel-explore .component:last-child{margin-bottom:.75rem}.nav-panel-explore .component .title{font-weight:500;font-weight:var(--body-font-weight-bold)}.nav-panel-explore .versions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-top:-.25rem;line-height:1;list-style:none}.nav-panel-explore .component .version{margin:.375rem .375rem 0 0}.nav-panel-explore .component .version a{border:1px solid #c1c1c1;border:1px solid var(--nav-border-color);border-radius:.25rem;opacity:.75;white-space:nowrap;padding:.125em .25em;display:inherit}.nav-panel-explore .component .is-current a{border-color:currentColor;opacity:.9;font-weight:500;font-weight:var(--body-font-weight-bold)}@media screen and (max-width:1023.5px){aside.toc.sidebar,aside>.toolbar{display:none}main>.content{overflow-x:auto}}@media screen and (min-width:1024px){main{-webkit-box-flex:1;-ms-flex:auto;flex:auto;min-width:0}main>.content{display:-webkit-box;display:-ms-flexbox;display:flex}aside.toc.embedded{display:none}aside.toc.sidebar{-webkit-box-flex:0;-ms-flex:0 0 9rem;flex:0 0 9rem;-ms-flex:0 0 var(--toc-width);flex:0 0 var(--toc-width);-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media screen and (min-width:1216px){aside.toc.sidebar{-ms-flex-preferred-size:12rem;flex-basis:12rem;-ms-flex-preferred-size:var(--toc-width--widescreen);flex-basis:var(--toc-width--widescreen)}}.toolbar{color:#fff;color:var(--toolbar-font-color);-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-color:#fdb940;background-color:var(--toolbar-background);-webkit-box-shadow:0 1px 0 #fff;box-shadow:0 1px 0 #fff;-webkit-box-shadow:0 1px 0 var(--toolbar-border-color);box-shadow:0 1px 0 var(--toolbar-border-color);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);height:2.5rem;height:var(--toolbar-height);-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;position:sticky;top:3.5rem;top:var(--navbar-height);z-index:2;z-index:var(--z-index-toolbar)}.toolbar a{color:inherit}.nav-toggle{background:url(../img/menu.svg) no-repeat 50% 47.5%;background-size:49%;border:none;outline:none;line-height:inherit;padding:0;height:2.5rem;height:var(--toolbar-height);width:2.5rem;width:var(--toolbar-height);margin-right:-.25rem}@media screen and (min-width:1024px){.nav-toggle{display:none}}.nav-toggle.is-active{background-image:url(../img/back.svg);background-size:41.5%}.home-link{display:block;background:url(../img/home-o.svg) no-repeat 50%;height:1.25rem;height:calc(var(--toolbar-height)/2);width:1.25rem;width:calc(var(--toolbar-height)/2);margin:.625rem;margin:calc(var(--toolbar-height)/4)}.home-link.is-current,.home-link:hover{background-image:url(../img/home.svg)}.edit-this-page{display:none;padding-right:.5rem}@media screen and (min-width:1024px){.edit-this-page{display:block}}.toolbar .edit-this-page a{color:#c1c1c1;color:var(--toolbar-muted-color)}.breadcrumbs{display:none;-webkit-box-flex:1;-ms-flex:1 1;flex:1 1;padding:0 .5rem 0 .75rem;line-height:1.35;line-height:var(--nav-line-height)}@media screen and (min-width:1024px){.breadcrumbs{display:block}}a+.breadcrumbs{padding-left:.05rem}.breadcrumbs ul{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0;list-style:none}.breadcrumbs li{display:inline;margin:0}.breadcrumbs li::after{content:"/";padding:0 .5rem}.breadcrumbs li:last-of-type::after{content:none}.page-versions{margin:0 .2rem 0 auto;position:relative;line-height:1}@media screen and (min-width:1024px){.page-versions{margin-right:.7rem}}.page-versions .version-menu-toggle{color:#c1c1c1;color:var(--page-version-menu-font-color);background:url(../img/chevron.svg) no-repeat;background-position:right .5rem top 50%;background-size:auto .75em;border:none;outline:none;line-height:inherit;padding:.5rem 1.5rem .5rem .5rem;position:relative;z-index:3;z-index:var(--z-index-page-version-menu)}.page-versions .version-menu{display:-webkit-box;display:-ms-flexbox;display:flex;min-width:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;color:#c1c1c1;color:var(--page-version-menu-font-color);background:-webkit-gradient(linear,left top,left bottom,from(#fdb940),to(#fdb940)) no-repeat;background:linear-gradient(180deg,#fdb940 0,#fdb940) no-repeat;background:-webkit-gradient(linear,left top,left bottom,from(var(--page-version-menu-background)),to(var(--page-version-menu-background))) no-repeat;background:linear-gradient(180deg,var(--page-version-menu-background) 0,var(--page-version-menu-background)) no-repeat;padding:1.375rem 1.5rem .5rem .5rem;position:absolute;top:0;right:0;white-space:nowrap}.page-versions:not(.is-active) .version-menu{display:none}.page-versions .version{display:block;padding-top:.5rem}.page-versions .version.is-current{display:none}.page-versions .version.is-missing{color:#8e8e8e;color:var(--page-version-missing-font-color);font-style:italic;text-decoration:none}.toc-menu{color:#5d5d5d;color:var(--toc-font-color)}.toc.sidebar .toc-menu{margin-right:.75rem;position:sticky;top:6rem;top:var(--toc-top)}.toc .toc-menu h3{color:#333;color:var(--toc-heading-font-color);font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);font-weight:500;font-weight:var(--body-font-weight-bold);line-height:1.3;margin:0 -1px;padding-bottom:.25rem}.toc.sidebar .toc-menu h3{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:2.5rem;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.toc .toc-menu ul{font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);line-height:1.2;line-height:var(--toc-line-height);list-style:none;margin:0;padding:0}.toc.sidebar .toc-menu ul{max-height:calc(100vh - 8.5rem);max-height:var(--toc-height);overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:none}@supports (scrollbar-width:none){.toc.sidebar .toc-menu ul{scrollbar-width:none}}.toc .toc-menu ul::-webkit-scrollbar{width:0;height:0}@media screen and (min-width:1024px){.toc .toc-menu h3{font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem)}.toc .toc-menu ul{font-size:.75rem;font-size:calc(13.5/var(--rem-base)*1rem)}}.toc .toc-menu li{margin:0}.toc .toc-menu li[data-level="2"] a{padding-left:1.25rem}.toc .toc-menu li[data-level="3"] a{padding-left:2rem}.toc .toc-menu a{color:inherit;border-left:2px solid #e1e1e1;border-left:2px solid var(--toc-border-color);display:inline-block;padding:.25rem 0 .25rem .5rem;text-decoration:none}.sidebar.toc .toc-menu a{display:block;outline:none}.toc .toc-menu a:hover{color:#1565c0;color:var(--link-font-color)}.toc .toc-menu a.is-active{border-left-color:#1565c0;border-left-color:var(--link-font-color);color:#333;color:var(--doc-font-color)}.sidebar.toc .toc-menu a:focus{background:#fafafa;background:var(--panel-background)}.toc .toc-menu .is-hidden-toc{display:none!important}.doc{color:#333;color:var(--doc-font-color);font-size:inherit;font-size:var(--doc-font-size);-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;line-height:1.6;line-height:var(--doc-line-height);margin:0 auto;margin:var(--doc-margin);max-width:40rem;max-width:var(--doc-max-width);padding:0 1rem 4rem}@media screen and (min-width:1024px){.doc{-webkit-box-flex:1;-ms-flex:auto;flex:auto;font-size:.94444rem;font-size:var(--doc-font-size--desktop);margin:0 2rem;margin:var(--doc-margin--desktop);max-width:46rem;max-width:var(--doc-max-width--desktop);min-width:0}}.doc h1,.doc h2,.doc h3,.doc h4,.doc h5,.doc h6{color:#191919;color:var(--heading-font-color);font-weight:400;font-weight:var(--heading-font-weight);-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;line-height:1.3;margin:1rem 0 0;font-family:Dosis,sans-serif;font-family:var(--heading-font-family)}.doc>h1.page:first-child{font-size:2rem;font-size:calc(36/var(--rem-base)*1rem);margin:1.5rem 0}@media screen and (min-width:769px){.doc>h1.page:first-child{margin-top:2.5rem}}.doc>h1.page:first-child+aside.toc.embedded{margin-top:-.5rem}.doc>h2#name+.sectionbody{margin-top:1rem}#preamble+.sect1,.doc .sect1+.sect1{margin-top:2rem}.doc h1.sect0{background:#f0f0f0;background:var(--abstract-background);font-size:1.8em;margin:1.5rem -1rem 0;padding:.5rem 1rem}.doc h2:not(.discrete){border-bottom:1px solid #e1e1e1;border-bottom:1px solid var(--section-divider-color);margin-left:-1rem;margin-right:-1rem;padding:.4rem 1rem .1rem}.doc h3:not(.discrete),.doc h4:not(.discrete){font-weight:500;font-weight:var(--alt-heading-font-weight)}.doc h1 .anchor,.doc h2 .anchor,.doc h3 .anchor,.doc h4 .anchor,.doc h5 .anchor,.doc h6 .anchor{position:absolute;text-decoration:none;width:1.75ex;margin-left:-1.5ex;visibility:hidden;font-size:.8em;font-weight:400;padding-top:.05em}.doc h1 .anchor::before,.doc h2 .anchor::before,.doc h3 .anchor::before,.doc h4 .anchor::before,.doc h5 .anchor::before,.doc h6 .anchor::before{content:"\00a7"}.doc h1:hover .anchor,.doc h2:hover .anchor,.doc h3:hover .anchor,.doc h4:hover .anchor,.doc h5:hover .anchor,.doc h6:hover .anchor{visibility:visible}.doc dl,.doc p{margin:0}.doc a{color:#1565c0;color:var(--link-font-color)}.doc a:hover{color:#104d92;color:var(--link_hover-font-color)}.doc a.bare{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc a.unresolved{color:#d32f2f;color:var(--link_unresolved-font-color)}.doc i.fa{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;font-style:normal}.doc .colist>table code,.doc p code,.doc thead code{color:#222;color:var(--code-font-color);background:#fafafa;background:var(--code-background);border-radius:.25em;font-size:.95em;padding:.125em .25em}.doc code,.doc pre{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc pre{font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);line-height:1.5;margin:0}.doc blockquote{margin:0}.doc .paragraph.lead>p{font-size:1rem;font-size:calc(18/var(--rem-base)*1rem)}.doc .right{float:right}.doc .left{float:left}.doc .float-gap.right{margin:0 1rem 1rem 0}.doc .float-gap.left{margin:0 0 1rem 1rem}.doc .float-group::after{content:"";display:table;clear:both}.doc .stretch{width:100%}.doc .underline{text-decoration:underline}.doc .line-through{text-decoration:line-through}.doc .dlist,.doc .exampleblock,.doc .hdlist,.doc .imageblock,.doc .listingblock,.doc .literalblock,.doc .olist,.doc .paragraph,.doc .partintro,.doc .quoteblock,.doc .sidebarblock,.doc .tabs,.doc .ulist,.doc .verseblock,.doc .videoblock,.doc details,.doc hr{margin:1rem 0 0}.doc table.tableblock{font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem)}.doc .tablecontainer,.doc .tablecontainer+*,.doc :not(.tablecontainer)>table.tableblock,.doc :not(.tablecontainer)>table.tableblock+*{margin-top:1.5rem}.doc p.tableblock+p.tableblock{margin-top:.5rem}.doc td.tableblock>.content>:first-child{margin-top:0}.doc table.tableblock td,.doc table.tableblock th{padding:.5rem}.doc table.tableblock,.doc table.tableblock>*>tr>*{border:0 solid #e1e1e1;border:0 solid var(--table-border-color)}.doc table.grid-all>*>tr>*{border-width:1px}.doc table.grid-cols>*>tr>*{border-width:0 1px}.doc table.grid-rows>*>tr>*{border-width:1px 0}.doc table.grid-all>thead th,.doc table.grid-rows>thead th{border-bottom-width:3px}.doc table.frame-all{border-width:1px}.doc table.frame-ends{border-width:1px 0}.doc table.frame-sides{border-width:0 1px}.doc table.frame-none>colgroup+*>:first-child>*,.doc table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}.doc table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}.doc table.frame-ends>*>tr>:first-child,.doc table.frame-none>*>tr>:first-child{border-left-width:0}.doc table.frame-ends>*>tr>:last-child,.doc table.frame-none>*>tr>:last-child{border-right-width:0}.doc table.stripes-all>tbody>tr,.doc table.stripes-even>tbody>tr:nth-of-type(2n),.doc table.stripes-hover>tbody>tr:hover,.doc table.stripes-odd>tbody>tr:nth-of-type(odd){background:#fafafa;background:var(--table-stripe-background)}.doc table.tableblock>tfoot{background:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background:linear-gradient(180deg,#f0f0f0 0,#fff);background:var(--table-footer-background)}.doc .halign-left{text-align:left}.doc .halign-right{text-align:right}.doc .halign-center{text-align:center}.doc .valign-top{vertical-align:top}.doc .valign-bottom{vertical-align:bottom}.doc .valign-middle{vertical-align:middle}.doc .admonitionblock{margin:1.4rem 0 0}.doc .admonitionblock p,.doc .admonitionblock td.content{font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem)}.doc .admonitionblock td.content>.title+*,.doc .admonitionblock td.content>:not(.title):first-child{margin-top:0}.doc .admonitionblock pre{font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem)}.doc .admonitionblock>table{table-layout:fixed;position:relative;width:100%}.doc .admonitionblock td.content{padding:1rem 1rem .75rem;background:#fafafa;background:var(--admonition-background);width:100%;word-wrap:anywhere}.doc .admonitionblock .icon{position:absolute;top:0;left:0;font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);padding:0 .5rem;height:1.25rem;line-height:1;font-weight:500;font-weight:var(--admonition-label-font-weight);text-transform:uppercase;border-radius:.45rem;-webkit-transform:translate(-.5rem,-50%);transform:translate(-.5rem,-50%)}.doc .admonitionblock.caution .icon{background-color:#a0439c;background-color:var(--caution-color);color:#fff;color:var(--caution-on-color)}.doc .admonitionblock.important .icon{background-color:#d32f2f;background-color:var(--important-color);color:#fff;color:var(--important-on-color)}.doc .admonitionblock.note .icon{background-color:#217ee7;background-color:var(--note-color);color:#fff;color:var(--note-on-color)}.doc .admonitionblock.tip .icon{background-color:#41af46;background-color:var(--tip-color);color:#fff;color:var(--tip-on-color)}.doc .admonitionblock.warning .icon{background-color:#e18114;background-color:var(--warning-color);color:#fff;color:var(--warning-on-color)}.doc .admonitionblock .icon i{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%}.doc .admonitionblock .icon i::after{content:attr(title)}.doc .imageblock,.doc .videoblock{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.doc .imageblock.text-left,.doc .videoblock.text-left{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.doc .imageblock.text-right,.doc .videoblock.text-right{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.doc .image>img,.doc .image>object,.doc .image>svg,.doc .imageblock img,.doc .imageblock object,.doc .imageblock svg{display:inline-block;height:auto;max-width:100%;vertical-align:middle}.doc .image:not(.left):not(.right)>img{margin-top:-.2em}.doc .videoblock iframe{max-width:100%;vertical-align:middle}#preamble .abstract blockquote{background:#f0f0f0;background:var(--abstract-background);border-left:5px solid #e1e1e1;border-left:5px solid var(--abstract-border-color);color:#4a4a4a;color:var(--abstract-font-color);font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);padding:.75em 1em}.doc .quoteblock,.doc .verseblock{background:#fafafa;background:var(--quote-background);border-left:5px solid #5d5d5d;border-left:5px solid var(--quote-border-color);color:#5d5d5d;color:var(--quote-font-color)}.doc .quoteblock{padding:.25rem 2rem 1.25rem}.doc .quoteblock .attribution{color:#8e8e8e;color:var(--quote-attribution-font-color);font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);margin-top:.75rem}.doc .quoteblock blockquote{margin-top:1rem}.doc .quoteblock .paragraph{font-style:italic}.doc .quoteblock cite{padding-left:1em}.doc .verseblock{font-size:1.15em;padding:1rem 2rem}.doc .verseblock pre{font-family:inherit;font-size:inherit}.doc ol,.doc ul{margin:0;padding:0 0 0 2rem}.doc ol.none,.doc ol.unnumbered,.doc ol.unstyled,.doc ul.checklist,.doc ul.no-bullet,.doc ul.none,.doc ul.unstyled{list-style-type:none}.doc ol.unnumbered,.doc ul.no-bullet{padding-left:1.25rem}.doc ol.unstyled,.doc ul.unstyled{padding-left:0}.doc ul.circle{list-style-type:circle}.doc ul.disc{list-style-type:disc}.doc ul.square{list-style-type:square}.doc ul.circle ul:not([class]),.doc ul.disc ul:not([class]),.doc ul.square ul:not([class]){list-style:inherit}.doc ol.arabic{list-style-type:decimal}.doc ol.decimal{list-style-type:decimal-leading-zero}.doc ol.loweralpha{list-style-type:lower-alpha}.doc ol.upperalpha{list-style-type:upper-alpha}.doc ol.lowerroman{list-style-type:lower-roman}.doc ol.upperroman{list-style-type:upper-roman}.doc ol.lowergreek{list-style-type:lower-greek}.doc ul.checklist{padding-left:1.75rem}.doc ul.checklist p>i.fa-check-square-o:first-child,.doc ul.checklist p>i.fa-square-o:first-child{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:1.25rem;margin-left:-1.25rem}.doc ul.checklist i.fa-check-square-o::before{content:"\2713"}.doc ul.checklist i.fa-square-o::before{content:"\274f"}.doc .dlist .dlist,.doc .dlist .olist,.doc .dlist .ulist,.doc .olist .dlist,.doc .olist .olist,.doc .olist .ulist,.doc .olist li+li,.doc .ulist .dlist,.doc .ulist .olist,.doc .ulist .ulist,.doc .ulist li+li{margin-top:.5rem}.doc .admonitionblock .listingblock,.doc .olist .listingblock,.doc .ulist .listingblock{padding:0}.doc .admonitionblock .title,.doc .exampleblock .title,.doc .imageblock .title,.doc .listingblock .title,.doc .literalblock .title,.doc .openblock .title,.doc .tableblock caption,.doc .videoblock .title{color:#5d5d5d;color:var(--caption-font-color);font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);font-style:italic;font-style:var(--caption-font-style);font-weight:500;font-weight:var(--caption-font-weight);-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;letter-spacing:.01em;padding-bottom:.075rem}.doc .tableblock caption{text-align:left}.doc .olist .title,.doc .ulist .title{font-style:italic;font-style:var(--caption-font-style);font-weight:500;font-weight:var(--caption-font-weight);margin-bottom:.25rem}.doc .imageblock .title{margin-top:.5rem;padding-bottom:0}.doc details{margin-left:1rem}.doc details>summary{display:block;position:relative;line-height:1.6;line-height:var(--doc-line-height);margin-bottom:.5rem}.doc details>summary::-webkit-details-marker{display:none}.doc details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;top:calc((var(--doc-line-height)*0.5 - .3)*1em);left:-1rem;-webkit-transform:translateX(15%);transform:translateX(15%)}.doc details[open]>summary::before{border-color:currentColor transparent transparent;border-width:.5rem .3rem 0;-webkit-transform:translateY(15%);transform:translateY(15%)}.doc details>summary::after{content:"";width:1rem;height:1em;position:absolute;top:.3em;top:calc((var(--doc-line-height)*0.5 - .5)*1em);left:-1rem}.doc details.result{margin-top:.25rem}.doc details.result>summary{color:#5d5d5d;color:var(--caption-font-color);font-style:italic;margin-bottom:0}.doc details.result>.content{margin-left:-1rem}.doc .exampleblock>.content,.doc details.result>.content{background:#fff;background:var(--example-background);border:.25rem solid #5d5d5d;border:.25rem solid var(--example-border-color);border-radius:.5rem;padding:.75rem}.doc .exampleblock>.content::after,.doc details.result>.content::after{content:"";display:table;clear:both}.doc .exampleblock>.content>:first-child,.doc details>.content>:first-child{margin-top:0}.doc .sidebarblock{background:#e1e1e1;background:var(--sidebar-background);border-radius:.75rem;padding:.75rem 1.5rem}.doc .sidebarblock>.content>.title{font-size:1.25rem;font-size:calc(22.5/var(--rem-base)*1rem);font-weight:500;font-weight:var(--alt-heading-font-weight);line-height:1.3;margin-bottom:-.3em;text-align:center}.doc .sidebarblock>.content>:not(.title):first-child{margin-top:0}.doc .listingblock.wrap pre,.doc .tableblock pre{white-space:pre-wrap}.doc .listingblock pre:not(.highlight),.doc .literalblock pre,.doc pre.highlight code{background:#fafafa;background:var(--pre-background);-webkit-box-shadow:inset 0 0 1.75px #e1e1e1;box-shadow:inset 0 0 1.75px #e1e1e1;-webkit-box-shadow:inset 0 0 1.75px var(--pre-border-color);box-shadow:inset 0 0 1.75px var(--pre-border-color);display:block;overflow-x:auto;padding:.875em}.doc .listingblock>.content{position:relative}.doc .source-toolbox{display:-webkit-box;display:-ms-flexbox;display:flex;visibility:hidden;position:absolute;top:.25rem;right:.5rem;color:grey;color:var(--pre-annotation-font-color);font-family:Lato,sans-serif;font-family:var(--body-font-family);font-size:.72222rem;font-size:calc(13/var(--rem-base)*1rem);line-height:1;white-space:nowrap;z-index:1}.doc .listingblock:hover .source-toolbox{visibility:visible}.doc .source-toolbox .source-lang{text-transform:uppercase;letter-spacing:.075em}.doc .source-toolbox>:not(:last-child)::after{content:"|";letter-spacing:0;padding:0 1ch}.doc .source-toolbox .copy-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:none;border:none;color:inherit;outline:none;padding:0;font-size:inherit;line-height:inherit;width:1em;height:1em}.doc .source-toolbox .copy-icon{-webkit-box-flex:0;-ms-flex:none;flex:none;width:inherit;height:inherit}.doc .source-toolbox img.copy-icon{-webkit-filter:invert(50.2%);filter:invert(50.2%)}.doc .source-toolbox svg.copy-icon{fill:currentColor}.doc .source-toolbox .copy-toast{-webkit-box-flex:0;-ms-flex:none;flex:none;position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-top:1em;background-color:#333;background-color:var(--doc-font-color);border-radius:.25em;padding:.5em;color:#fff;color:var(--color-white);cursor:auto;opacity:0;-webkit-transition:opacity .5s ease .5s;transition:opacity .5s ease .5s}.doc .source-toolbox .copy-toast::after{content:"";position:absolute;top:0;width:1em;height:1em;border:.55em solid transparent;border-left:.55em solid var(--doc-font-color);-webkit-transform:rotate(-90deg) translateX(50%) translateY(50%);transform:rotate(-90deg) translateX(50%) translateY(50%);-webkit-transform-origin:left;transform-origin:left}.doc .source-toolbox .copy-button.clicked .copy-toast{opacity:1;-webkit-transition:none;transition:none}.doc .language-console .hljs-meta{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.doc .dlist dt{font-style:italic}.doc .dlist dd{margin:0 0 .25rem 1.5rem}.doc .dlist dd:last-of-type{margin-bottom:0}.doc td.hdlist1,.doc td.hdlist2{padding:.5rem 0 0;vertical-align:top}.doc tr:first-child>.hdlist1,.doc tr:first-child>.hdlist2{padding-top:0}.doc td.hdlist1{font-weight:500;font-weight:var(--body-font-weight-bold);padding-right:.25rem}.doc td.hdlist2{padding-left:.25rem}.doc .colist{font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);margin:.25rem 0 -.25rem}.doc .colist>table>tbody>tr>:first-child,.doc .colist>table>tr>:first-child{padding:.25em .5rem 0;vertical-align:top}.doc .colist>table>tbody>tr>:last-child,.doc .colist>table>tr>:last-child{padding:.25rem 0}.doc .conum[data-value]{border:1px solid;border-radius:100%;display:inline-block;font-family:Lato,sans-serif;font-family:var(--body-font-family);font-size:.75rem;font-size:calc(13.5/var(--rem-base)*1rem);font-style:normal;line-height:1.2;text-align:center;width:1.25em;height:1.25em;letter-spacing:-.25ex;text-indent:-.25ex}.doc .conum[data-value]::after{content:attr(data-value)}.doc .conum[data-value]+b{display:none}.doc hr{border:solid #e1e1e1;border:solid var(--section-divider-color);border-width:2px 0 0;height:0}.doc b.button{white-space:nowrap}.doc b.button::before{content:"[";padding-right:.25em}.doc b.button::after{content:"]";padding-left:.25em}.doc kbd{display:inline-block;font-size:.66667rem;font-size:calc(12/var(--rem-base)*1rem);background:#fafafa;background:var(--kbd-background);border:1px solid #c1c1c1;border:1px solid var(--kbd-border-color);border-radius:.25em;-webkit-box-shadow:0 1px 0 #c1c1c1,0 0 0 .1em #fff inset;box-shadow:0 1px 0 #c1c1c1,inset 0 0 0 .1em #fff;-webkit-box-shadow:0 1px 0 var(--kbd-border-color),0 0 0 .1em var(--body-background) inset;box-shadow:0 1px 0 var(--kbd-border-color),0 0 0 .1em var(--body-background) inset;padding:.25em .5em;vertical-align:text-bottom;white-space:nowrap}.doc .keyseq,.doc kbd{line-height:1}.doc .keyseq{font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem)}.doc .keyseq kbd{margin:0 .125em}.doc .keyseq kbd:first-child{margin-left:0}.doc .keyseq kbd:last-child{margin-right:0}.doc .menuseq,.doc .path{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.doc .menuseq i.caret::before{content:"\203a";font-size:1.1em;font-weight:500;font-weight:var(--body-font-weight-bold);line-height:.90909}.doc :not(pre).nowrap{white-space:nowrap}.doc .nobreak{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;word-wrap:normal}#footnotes{font-size:.85em;line-height:1.5;margin:2rem -.5rem 0}.doc td.tableblock>.content #footnotes{margin:2rem 0 0}#footnotes hr{border-top-width:1px;margin-top:0;width:20%}#footnotes .footnote{margin:.5em 0 0 1em}#footnotes .footnote+.footnote{margin-top:.25em}#footnotes .footnote>a:first-of-type{display:inline-block;margin-left:-2em;text-align:right;width:1.5em}nav.pagination{border-top:1px solid #fff;border-top:1px solid var(--toolbar-border-color);line-height:1;margin:2rem -1rem -1rem;padding:.75rem 1rem 0}nav.pagination,nav.pagination span{display:-webkit-box;display:-ms-flexbox;display:flex}nav.pagination span{-webkit-box-flex:50%;-ms-flex:50%;flex:50%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}nav.pagination .prev{padding-right:.5rem}nav.pagination .next{margin-left:auto;padding-left:.5rem;text-align:right}nav.pagination span::before{color:#c1c1c1;color:var(--toolbar-muted-color);font-size:.75em;padding-bottom:.1em}nav.pagination .prev::before{content:"Prev"}nav.pagination .next::before{content:"Next"}nav.pagination a{font-weight:500;font-weight:var(--body-font-weight-bold);line-height:1.3;position:relative}nav.pagination a::after,nav.pagination a::before{color:#c1c1c1;color:var(--toolbar-muted-color);font-weight:400;font-size:1.5em;line-height:.75;position:absolute;top:0;width:1rem}nav.pagination .prev a::before{content:"\2039";-webkit-transform:translateX(-100%);transform:translateX(-100%)}nav.pagination .next a::after{content:"\203a"}html.is-clipped--navbar{overflow-y:hidden}body{padding-top:3.5rem;padding-top:var(--navbar-height)}.navbar{background:#fff;background:var(--navbar-background);color:#222;color:var(--navbar-font-color);font-size:.88889rem;font-size:calc(16/var(--rem-base)*1rem);height:3.5rem;height:var(--navbar-height);position:fixed;top:0;width:100%;z-index:4;z-index:var(--z-index-navbar)}.navbar a{text-decoration:none}.navbar-brand{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:auto;flex:auto;padding-left:1rem}.navbar-brand .navbar-item{color:#222;color:var(--navbar-font-color)}.navbar-brand .navbar-item:first-child{-ms-flex-item-align:center;align-self:center;padding:0;font-size:1.22222rem;font-size:calc(22/var(--rem-base)*1rem);-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:1}.navbar-brand .navbar-item:first-child a{color:inherit;word-wrap:normal}.navbar-brand .navbar-item:first-child :not(:last-child){padding-right:.375rem}.navbar-brand .navbar-item.search{-webkit-box-flex:1;-ms-flex:auto;flex:auto;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}#site-title{display:block;width:150px;height:45px;background:url(../img/microprofile-horizontal-white_bg-.svg) no-repeat 50%;font-size:0}#search-input{color:#333;font-family:inherit;font-size:.95rem;width:150px;border:1px solid #dbdbdb;border-radius:.1em;line-height:1.5;padding:0 .25em}#search-input:disabled{background-color:#dbdbdb;cursor:not-allowed;pointer-events:all!important}#search-input:disabled::-webkit-input-placeholder{color:#4c4c4c}#search-input:disabled::-moz-placeholder{color:#4c4c4c}#search-input:disabled:-ms-input-placeholder{color:#4c4c4c}#search-input:disabled::-ms-input-placeholder{color:#4c4c4c}#search-input:disabled::placeholder{color:#4c4c4c}#search-input:focus{outline:none}.navbar-burger{background:none;border:none;outline:none;line-height:1;position:relative;width:3rem;padding:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-left:auto;min-width:0}.navbar-burger span{background-color:#222;background-color:var(--navbar-font-color);height:2px;width:1rem}.navbar-burger:not(.is-active) span{-webkit-transition:opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out;transition:opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out;transition:transform .25s ease-out,opacity 0s .25s,margin-top .25s ease-out .25s;transition:transform .25s ease-out,opacity 0s .25s,margin-top .25s ease-out .25s,-webkit-transform .25s ease-out}.navbar-burger span+span{margin-top:.25rem}.navbar-burger.is-active span+span{margin-top:-2px}.navbar-burger.is-active span:first-child{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.navbar-item,.navbar-link{color:#222;color:var(--navbar-menu-font-color);display:block;line-height:1.6;line-height:var(--doc-line-height);padding:.5rem 1rem}.navbar-item.has-dropdown{padding:0}.navbar-item .icon{width:1.25rem;height:1.25rem;display:block}.navbar-item .icon img,.navbar-item .icon svg{fill:currentColor;width:inherit;height:inherit}.navbar-link{padding-right:2.5em}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-dropdown .navbar-item.has-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-dropdown .navbar-item small{color:#c1c1c1;color:var(--toolbar-muted-color);font-size:.66667rem;font-size:calc(12/var(--rem-base)*1rem)}.navbar-divider{background-color:#e1e1e1;background-color:var(--navbar-menu-border-color);border:none;height:1px;margin:.25rem 0}.navbar .button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;background:#f98200;background:var(--navbar-button-background);border:1px solid #f98200;border:1px solid var(--navbar-button-border-color);border-radius:.15rem;height:1.75rem;color:#fff;color:var(--navbar-button-font-color);padding:0 .75em;white-space:nowrap}.navbar-theme-switcher,.navbar .button{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-theme-switcher{display:-webkit-box;display:-ms-flexbox;display:flex;padding-right:1rem}@media screen and (max-width:768.5px){.navbar-brand .navbar-item.search{padding-left:0;padding-right:0}}@media screen and (min-width:769px){#search-input{width:200px}}@media screen and (max-width:1023.5px){.navbar-brand{height:inherit}.navbar-brand .navbar-item{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex}.navbar-menu{background:#fff;background:var(--navbar-menu-background);-webkit-box-shadow:0 8px 16px rgba(10,10,10,.1);box-shadow:0 8px 16px rgba(10,10,10,.1);max-height:calc(100vh - 3.5rem);max-height:var(--body-min-height);overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:none;padding:.5rem 0}.navbar-menu:not(.is-active){display:none}.navbar-menu .navbar-link:hover,.navbar-menu a.navbar-item:hover{background:#f5f5f5;background:var(--navbar-menu_hover-background)}.navbar-theme-switcher{padding:.5rem 1rem}}@media screen and (min-width:1024px){.navbar-burger{display:none}.navbar,.navbar-end,.navbar-item,.navbar-link,.navbar-menu{display:-webkit-box;display:-ms-flexbox;display:flex}.navbar-item,.navbar-link{position:relative;-webkit-box-flex:0;-ms-flex:none;flex:none}.navbar-item:not(.has-dropdown),.navbar-link{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-link::after{border-width:0 0 1px 1px;border-style:solid;content:"";display:block;height:.5em;pointer-events:none;position:absolute;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);width:.5em;margin-top:-.375em;right:1.125em;top:50%}.navbar-end .navbar-link,.navbar-end>.navbar-item{color:#222;color:var(--navbar-font-color)}.navbar-end .navbar-item.has-dropdown:hover .navbar-link,.navbar-end .navbar-link:hover,.navbar-end>a.navbar-item:hover{background:#131660;background:var(--navbar_hover-background);color:#222;color:var(--navbar-font-color)}.navbar-end .navbar-link::after{border-color:currentColor}.navbar-dropdown{background:#fff;background:var(--navbar-menu-background);border:1px solid #e1e1e1;border:1px solid var(--navbar-menu-border-color);border-top:none;border-radius:0 0 .25rem .25rem;display:none;top:100%;left:0;min-width:100%;position:absolute}.navbar-dropdown .navbar-item{padding:.5rem 3rem .5rem 1rem;white-space:nowrap}.navbar-dropdown .navbar-item small{position:relative;right:-2rem}.navbar-dropdown .navbar-item:last-child{border-radius:inherit}.navbar-dropdown.is-right{left:auto;right:0}.navbar-dropdown a.navbar-item:hover{background:#f5f5f5;background:var(--navbar-menu_hover-background)}}footer.footer{background-color:#e1e1e1;background-color:var(--footer-background);color:#5d5d5d;color:var(--footer-font-color);font-size:.83333rem;font-size:calc(15/var(--rem-base)*1rem);line-height:1.6;line-height:var(--footer-line-height);padding:1.5rem}.footer p{margin:.5rem 0}.footer a{color:#191919;color:var(--footer-link-font-color)} +/*! + Theme: GitHub + Description: Light theme as seen on github.com + Source: https://highlightjs.org/static/demo/styles/github.css +*/.hljs{color:#24292e;background:#fff;font-weight:400}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49;font-weight:400}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1;font-weight:400}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5;font-weight:400}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62;font-weight:400}.hljs-built_in,.hljs-symbol{color:#e36209;font-weight:400}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d;font-weight:400}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-tag{color:#22863a;font-weight:400}.hljs-subst{color:#24292e;font-weight:400}.hljs-section{color:#005cc5;font-weight:500;font-weight:var(--monospace-font-weight-bold)}.hljs-bullet{color:#735c0f;font-weight:400}.hljs-emphasis{color:#24292e;font-style:italic;font-weight:400}.hljs-strong{color:#24292e;font-weight:500;font-weight:var(--monospace-font-weight-bold)}.hljs-addition{color:#22863a;background-color:#f0fff4;font-weight:400}.hljs-deletion{color:#b31d28;background-color:#ffeef0;font-weight:400} +/*! + Theme: GitHub Dark Dimmed + Description: Dark dimmed theme as seen on github.com + Source: https://highlightjs.org/static/demo/styles/github-dark-dimmed.css +*/html.theme-dark .hljs{color:#adbac7;background:#22272e;font-weight:400}html.theme-dark .hljs-doctag,html.theme-dark .hljs-keyword,html.theme-dark .hljs-meta .hljs-keyword,html.theme-dark .hljs-template-tag,html.theme-dark .hljs-template-variable,html.theme-dark .hljs-type,html.theme-dark .hljs-variable.language_{color:#f47067;font-weight:400}html.theme-dark .hljs-title,html.theme-dark .hljs-title.class_,html.theme-dark .hljs-title.class_.inherited__,html.theme-dark .hljs-title.function_{color:#dcbdfb;font-weight:400}html.theme-dark .hljs-attr,html.theme-dark .hljs-attribute,html.theme-dark .hljs-literal,html.theme-dark .hljs-meta,html.theme-dark .hljs-number,html.theme-dark .hljs-operator,html.theme-dark .hljs-selector-attr,html.theme-dark .hljs-selector-class,html.theme-dark .hljs-selector-id,html.theme-dark .hljs-variable{color:#6cb6ff;font-weight:400}html.theme-dark .hljs-meta .hljs-string,html.theme-dark .hljs-regexp,html.theme-dark .hljs-string{color:#96d0ff;font-weight:400}html.theme-dark .hljs-built_in,html.theme-dark .hljs-symbol{color:#f69d50;font-weight:400}html.theme-dark .hljs-code,html.theme-dark .hljs-comment,html.theme-dark .hljs-formula{color:#768390;font-weight:400}html.theme-dark .hljs-name,html.theme-dark .hljs-quote,html.theme-dark .hljs-selector-pseudo,html.theme-dark .hljs-selector-tag,html.theme-dark .hljs-tag{color:#8ddb8c;font-weight:400}html.theme-dark .hljs-subst{color:#adbac7;font-weight:400}html.theme-dark .hljs-section{color:#316dca;font-weight:500;font-weight:var(--monospace-font-weight-bold)}html.theme-dark .hljs-bullet{color:#eac55f;font-weight:400}html.theme-dark .hljs-emphasis{color:#adbac7;font-style:italic;font-weight:400}html.theme-dark .hljs-strong{color:#adbac7;font-weight:500;font-weight:var(--monospace-font-weight-bold)}html.theme-dark .hljs-addition{color:#b4f1b4;background-color:#1b4721;font-weight:400}html.theme-dark .hljs-deletion{color:#ffd8d3;background-color:#78191b;font-weight:400}@page{margin:.5in}@media print{.hide-for-print{display:none!important}html{font-size:.9375em;font-size:var(--body-font-size--print)}a{color:inherit!important;text-decoration:underline}a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none}img,object,svg,tr{page-break-inside:avoid}thead{display:table-header-group}pre{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;white-space:pre-wrap}body{padding-top:2rem}.navbar{background:none;color:inherit;position:absolute}.navbar *{color:inherit!important}.nav-container,.navbar>:not(.navbar-brand),.toolbar,aside.toc,nav.pagination{display:none}.doc{color:inherit;margin:auto;max-width:none;padding-bottom:2rem}.doc .admonitionblock td.icon{-webkit-print-color-adjust:exact;color-adjust:exact}.doc .listingblock code[data-lang]::before{display:block}footer.footer{background:none;border-top:1px solid #e1e1e1;border-top:1px solid var(--panel-border-color);color:#8e8e8e;color:var(--quote-attribution-font-color);padding:.25rem .5rem 0}.footer *{color:inherit}}.switch{display:-webkit-box;display:-ms-flexbox;display:flex}.switch input{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;position:relative;width:2.75rem;height:1.5rem;background:no-repeat url(../img/octicons-16.svg#view-sun) 90% 50%/1rem 1rem,no-repeat url(../img/octicons-16.svg#view-moon) 10% 50%/1rem 1rem;background-color:#fff;background-color:var(--navbar-switch-background);border:1px solid #3d3d3d;border:1px solid var(--navbar-switch-button-background);border-radius:1rem;outline:none}.switch input::before{content:"";position:absolute;left:.175em;top:.175em;height:1.125rem;width:1.125rem;border-radius:1rem;background-color:#3d3d3d;background-color:var(--navbar-switch-button-background);-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s}.switch:hover input::before{background-color:#58595b;background-color:var(--navbar-switch-button_hover-background)}.switch input:checked::before{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.switch label{display:none}html.theme-dark{--color-white:#000;--color-smoke-10:#191919;--color-smoke-30:#222;--color-smoke-50:#333;--color-smoke-70:#424242;--color-smoke-90:#4a4a4a;--color-gray-10:#5d5d5d;--color-gray-30:grey;--color-gray-40:#8e8e8e;--color-gray-50:#9c9c9c;--color-gray-70:#c1c1c1;--color-jet-20:#e1e1e1;--color-jet-30:#f0f0f0;--color-jet-50:#f5f5f5;--color-jet-70:#fafafa;--color-jet-80:#fefefe;--color-black:#fff;--toolbar-background:var(--color-brand-grey);--toolbar-muted-color:var(--color-gray-70);--link-font-color:#55a4f1;--link_hover-font-color:#88d6ff}html.theme-dark #site-title{background-image:url(../img/microprofile-logo-no_tagline-white.svg)}body .navbar-item.search{padding-right:0}body #search-input{background-color:#fff;background-color:var(--color-white);color:#5d5d5d;color:var(--color-gray-70);border-color:#e1e1e1;border-color:var(--color-smoke-90);height:1.75rem;border-radius:.15rem;padding:.25em}body #search-input:disabled{background-color:#f0f0f0;background-color:var(--color-smoke-70)}body #search-input::-webkit-input-placeholder{color:#4a4a4a;color:var(--color-jet-20)}body #search-input::-moz-placeholder{color:#4a4a4a;color:var(--color-jet-20)}body #search-input:-ms-input-placeholder{color:#4a4a4a;color:var(--color-jet-20)}body #search-input::-ms-input-placeholder{color:#4a4a4a;color:var(--color-jet-20)}body #search-input::placeholder{color:#4a4a4a;color:var(--color-jet-20)}body #search-input:disabled::-webkit-input-placeholder{color:#8e8e8e;color:var(--color-gray-40)}body #search-input:disabled::-moz-placeholder{color:#8e8e8e;color:var(--color-gray-40)}body #search-input:disabled:-ms-input-placeholder{color:#8e8e8e;color:var(--color-gray-40)}body #search-input:disabled::-ms-input-placeholder{color:#8e8e8e;color:var(--color-gray-40)}body #search-input:disabled::placeholder{color:#8e8e8e;color:var(--color-gray-40)}body .search-result-dropdown-menu{margin-top:-.5em;border-radius:.15rem}body .search-result-dataset{background-color:#fff;background-color:var(--color-white);border-color:#c1c1c1;border-color:var(--color-gray-10)}body .search-result-component-header{color:#191919;color:var(--color-jet-80);border-bottom-color:#c1c1c1;border-bottom-color:var(--color-gray-10)}body .search-result-document-title{color:#191919;color:var(--color-jet-80);border-right-color:#c1c1c1;border-right-color:var(--color-gray-10)}body .search-result-document-hit{color:#5d5d5d;color:var(--color-gray-70)}body .search-result-document-hit .search-result-section-title{color:#333;color:var(--color-jet-50)}body .search-result-document-hit .search-result-highlight{color:#1565c0;color:var(--link-font-color)} \ No newline at end of file diff --git a/build/site/_/font/open-sans-latin-300-normal.woff b/build/site/_/font/open-sans-latin-300-normal.woff new file mode 100644 index 00000000..919bfcb2 Binary files /dev/null and b/build/site/_/font/open-sans-latin-300-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-300-normal.woff2 b/build/site/_/font/open-sans-latin-300-normal.woff2 new file mode 100644 index 00000000..67a6cffc Binary files /dev/null and b/build/site/_/font/open-sans-latin-300-normal.woff2 differ diff --git a/build/site/_/font/open-sans-latin-400-italic.woff b/build/site/_/font/open-sans-latin-400-italic.woff new file mode 100644 index 00000000..a296f033 Binary files /dev/null and b/build/site/_/font/open-sans-latin-400-italic.woff differ diff --git a/build/site/_/font/open-sans-latin-400-italic.woff2 b/build/site/_/font/open-sans-latin-400-italic.woff2 new file mode 100644 index 00000000..8da39ae4 Binary files /dev/null and b/build/site/_/font/open-sans-latin-400-italic.woff2 differ diff --git a/build/site/_/font/open-sans-latin-400-normal.woff b/build/site/_/font/open-sans-latin-400-normal.woff new file mode 100644 index 00000000..b0836266 Binary files /dev/null and b/build/site/_/font/open-sans-latin-400-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-400-normal.woff2 b/build/site/_/font/open-sans-latin-400-normal.woff2 new file mode 100644 index 00000000..15339ea9 Binary files /dev/null and b/build/site/_/font/open-sans-latin-400-normal.woff2 differ diff --git a/build/site/_/font/open-sans-latin-500-normal.woff b/build/site/_/font/open-sans-latin-500-normal.woff new file mode 100644 index 00000000..f14241b1 Binary files /dev/null and b/build/site/_/font/open-sans-latin-500-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-500-normal.woff2 b/build/site/_/font/open-sans-latin-500-normal.woff2 new file mode 100644 index 00000000..7832bea4 Binary files /dev/null and b/build/site/_/font/open-sans-latin-500-normal.woff2 differ diff --git a/build/site/_/font/open-sans-latin-600-normal.woff b/build/site/_/font/open-sans-latin-600-normal.woff new file mode 100644 index 00000000..b4ee4e39 Binary files /dev/null and b/build/site/_/font/open-sans-latin-600-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-600-normal.woff2 b/build/site/_/font/open-sans-latin-600-normal.woff2 new file mode 100644 index 00000000..2eb82d84 Binary files /dev/null and b/build/site/_/font/open-sans-latin-600-normal.woff2 differ diff --git a/build/site/_/font/open-sans-latin-700-normal.woff b/build/site/_/font/open-sans-latin-700-normal.woff new file mode 100644 index 00000000..3b6ed019 Binary files /dev/null and b/build/site/_/font/open-sans-latin-700-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-700-normal.woff2 b/build/site/_/font/open-sans-latin-700-normal.woff2 new file mode 100644 index 00000000..e832c2a0 Binary files /dev/null and b/build/site/_/font/open-sans-latin-700-normal.woff2 differ diff --git a/build/site/_/font/open-sans-latin-800-normal.woff b/build/site/_/font/open-sans-latin-800-normal.woff new file mode 100644 index 00000000..d4ae778f Binary files /dev/null and b/build/site/_/font/open-sans-latin-800-normal.woff differ diff --git a/build/site/_/font/open-sans-latin-800-normal.woff2 b/build/site/_/font/open-sans-latin-800-normal.woff2 new file mode 100644 index 00000000..fd7ad3d6 Binary files /dev/null and b/build/site/_/font/open-sans-latin-800-normal.woff2 differ diff --git a/build/site/_/font/roboto-mono-latin-400-normal.woff b/build/site/_/font/roboto-mono-latin-400-normal.woff new file mode 100644 index 00000000..be3eb4c4 Binary files /dev/null and b/build/site/_/font/roboto-mono-latin-400-normal.woff differ diff --git a/build/site/_/font/roboto-mono-latin-400-normal.woff2 b/build/site/_/font/roboto-mono-latin-400-normal.woff2 new file mode 100644 index 00000000..f8894bab Binary files /dev/null and b/build/site/_/font/roboto-mono-latin-400-normal.woff2 differ diff --git a/build/site/_/font/roboto-mono-latin-500-normal.woff b/build/site/_/font/roboto-mono-latin-500-normal.woff new file mode 100644 index 00000000..43ca6a1b Binary files /dev/null and b/build/site/_/font/roboto-mono-latin-500-normal.woff differ diff --git a/build/site/_/font/roboto-mono-latin-500-normal.woff2 b/build/site/_/font/roboto-mono-latin-500-normal.woff2 new file mode 100644 index 00000000..b4f2bf8c Binary files /dev/null and b/build/site/_/font/roboto-mono-latin-500-normal.woff2 differ diff --git a/build/site/_/img/back.svg b/build/site/_/img/back.svg new file mode 100644 index 00000000..31a8b532 --- /dev/null +++ b/build/site/_/img/back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/caret.svg b/build/site/_/img/caret.svg new file mode 100644 index 00000000..8b7b4f4e --- /dev/null +++ b/build/site/_/img/caret.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/chevron.svg b/build/site/_/img/chevron.svg new file mode 100644 index 00000000..d420e511 --- /dev/null +++ b/build/site/_/img/chevron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/favicon.png b/build/site/_/img/favicon.png new file mode 100644 index 00000000..1276abca Binary files /dev/null and b/build/site/_/img/favicon.png differ diff --git a/build/site/_/img/favicon.svg b/build/site/_/img/favicon.svg new file mode 100644 index 00000000..086680cd --- /dev/null +++ b/build/site/_/img/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/home-o.svg b/build/site/_/img/home-o.svg new file mode 100644 index 00000000..13874e1d --- /dev/null +++ b/build/site/_/img/home-o.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/home.svg b/build/site/_/img/home.svg new file mode 100644 index 00000000..584d26c4 --- /dev/null +++ b/build/site/_/img/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/menu.svg b/build/site/_/img/menu.svg new file mode 100644 index 00000000..c00fc945 --- /dev/null +++ b/build/site/_/img/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/microprofile-horizontal-white_bg-.svg b/build/site/_/img/microprofile-horizontal-white_bg-.svg new file mode 100644 index 00000000..8a12550f --- /dev/null +++ b/build/site/_/img/microprofile-horizontal-white_bg-.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/microprofile-logo-no_tagline-white.svg b/build/site/_/img/microprofile-logo-no_tagline-white.svg new file mode 100644 index 00000000..16c9de03 --- /dev/null +++ b/build/site/_/img/microprofile-logo-no_tagline-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build/site/_/img/octicons-16.svg b/build/site/_/img/octicons-16.svg new file mode 100644 index 00000000..0b8d2849 --- /dev/null +++ b/build/site/_/img/octicons-16.svg @@ -0,0 +1 @@ +Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT \ No newline at end of file diff --git a/build/site/_/js/search-ui.js b/build/site/_/js/search-ui.js new file mode 100644 index 00000000..0c964e18 --- /dev/null +++ b/build/site/_/js/search-ui.js @@ -0,0 +1,477 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.antoraSearch = {})); +})(this, (function (exports) { 'use strict'; + + /** + * Splitting the text by the given positions. + * The text within the positions getting the type "mark", all other text gets the type "text". + * @param {string} text + * @param {Object[]} positions + * @param {number} positions.start + * @param {number} positions.length + * @param {number} snippetLength Maximum text length for text in the result. + * @returns {[{text: string, type: string}]} + */ + function buildHighlightedText (text, positions, snippetLength) { + const textLength = text.length; + const validPositions = positions.filter( + (position) => position.length > 0 && position.start + position.length <= textLength + ); + + if (validPositions.length === 0) { + return [ + { + type: 'text', + text: + text.slice(0, snippetLength >= textLength ? textLength : snippetLength) + + (snippetLength < textLength ? '...' : ''), + }, + ] + } + + const orderedPositions = validPositions.sort((p1, p2) => p1.start - p2.start); + const range = { + start: 0, + end: textLength, + }; + const firstPosition = orderedPositions[0]; + if (snippetLength && text.length > snippetLength) { + const firstPositionStart = firstPosition.start; + const firstPositionLength = firstPosition.length; + const firstPositionEnd = firstPositionStart + firstPositionLength; + + range.start = firstPositionStart - snippetLength < 0 ? 0 : firstPositionStart - snippetLength; + range.end = firstPositionEnd + snippetLength > textLength ? textLength : firstPositionEnd + snippetLength; + } + const nodes = []; + if (firstPosition.start > 0) { + nodes.push({ + type: 'text', + text: (range.start > 0 ? '...' : '') + text.slice(range.start, firstPosition.start), + }); + } + let lastEndPosition = 0; + const positionsWithinRange = orderedPositions.filter( + (position) => position.start >= range.start && position.start + position.length <= range.end + ); + + for (const position of positionsWithinRange) { + const start = position.start; + const length = position.length; + const end = start + length; + if (lastEndPosition > 0) { + // create text Node from the last end position to the start of the current position + nodes.push({ + type: 'text', + text: text.slice(lastEndPosition, start), + }); + } + nodes.push({ + type: 'mark', + text: text.slice(start, end), + }); + lastEndPosition = end; + } + if (lastEndPosition < range.end) { + nodes.push({ + type: 'text', + text: text.slice(lastEndPosition, range.end) + (range.end < textLength ? '...' : ''), + }); + } + + return nodes + } + + /** + * Taken and adapted from: https://github.com/olivernn/lunr.js/blob/aa5a878f62a6bba1e8e5b95714899e17e8150b38/lib/tokenizer.js#L24-L67 + * @param lunr + * @param text + * @param term + * @return {{start: number, length: number}} + */ + function findTermPosition (lunr, term, text) { + const str = text.toLowerCase(); + const index = str.indexOf(term); + + if (index >= 0) { + // extend term until word boundary to return the entire word + const boundaries = str.substr(index).match(/^[\p{Alpha}]+/u); + if (boundaries !== null && boundaries.length >= 0) { + return { + start: index, + length: boundaries[0].length, + } + } + } + + // Not found + return { + start: 0, + length: 0, + } + } + + /* global CustomEvent, globalThis */ + + const config = document.getElementById('search-ui-script').dataset; + const snippetLength = parseInt(config.snippetLength || 100, 10); + const siteRootPath = config.siteRootPath || ''; + appendStylesheet(config.stylesheet); + const searchInput = document.getElementById('search-input'); + const searchResultContainer = document.createElement('div'); + searchResultContainer.classList.add('search-result-dropdown-menu'); + searchInput.parentNode.appendChild(searchResultContainer); + const facetFilterInput = document.querySelector('#search-field input[type=checkbox][data-facet-filter]'); + + function appendStylesheet (href) { + if (!href) return + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + document.head.appendChild(link); + } + + function highlightPageTitle (title, terms) { + const positions = getTermPosition(title, terms); + return buildHighlightedText(title, positions, snippetLength) + } + + function highlightSectionTitle (sectionTitle, terms) { + if (sectionTitle) { + const text = sectionTitle.text; + const positions = getTermPosition(text, terms); + return buildHighlightedText(text, positions, snippetLength) + } + return [] + } + + function highlightKeyword (doc, terms) { + const keyword = doc.keyword; + if (keyword) { + const positions = getTermPosition(keyword, terms); + return buildHighlightedText(keyword, positions, snippetLength) + } + return [] + } + + function highlightText (doc, terms) { + const text = doc.text; + const positions = getTermPosition(text, terms); + return buildHighlightedText(text, positions, snippetLength) + } + + function getTermPosition (text, terms) { + const positions = terms + .map((term) => findTermPosition(globalThis.lunr, term, text)) + .filter((position) => position.length > 0) + .sort((p1, p2) => p1.start - p2.start); + + if (positions.length === 0) { + return [] + } + return positions + } + + function highlightHit (searchMetadata, sectionTitle, doc) { + const terms = {}; + for (const term in searchMetadata) { + const fields = searchMetadata[term]; + for (const field in fields) { + terms[field] = [...(terms[field] || []), term]; + } + } + return { + pageTitleNodes: highlightPageTitle(doc.title, terms.title || []), + sectionTitleNodes: highlightSectionTitle(sectionTitle, terms.title || []), + pageContentNodes: highlightText(doc, terms.text || []), + pageKeywordNodes: highlightKeyword(doc, terms.keyword || []), + } + } + + function createSearchResult (result, store, searchResultDataset) { + let currentComponent; + result.forEach(function (item) { + const ids = item.ref.split('-'); + const docId = ids[0]; + const doc = store.documents[docId]; + let sectionTitle; + if (ids.length > 1) { + const titleId = ids[1]; + sectionTitle = doc.titles.filter(function (item) { + return String(item.id) === titleId + })[0]; + } + const metadata = item.matchData.metadata; + const highlightingResult = highlightHit(metadata, sectionTitle, doc); + const componentVersion = store.componentVersions[`${doc.component}/${doc.version}`]; + if (componentVersion !== undefined && currentComponent !== componentVersion) { + const searchResultComponentHeader = document.createElement('div'); + searchResultComponentHeader.classList.add('search-result-component-header'); + const { title, displayVersion } = componentVersion; + const componentVersionText = `${title}${doc.version && displayVersion ? ` ${displayVersion}` : ''}`; + searchResultComponentHeader.appendChild(document.createTextNode(componentVersionText)); + searchResultDataset.appendChild(searchResultComponentHeader); + currentComponent = componentVersion; + } + searchResultDataset.appendChild(createSearchResultItem(doc, sectionTitle, item, highlightingResult)); + }); + } + + function createSearchResultItem (doc, sectionTitle, item, highlightingResult) { + const documentTitle = document.createElement('div'); + documentTitle.classList.add('search-result-document-title'); + highlightingResult.pageTitleNodes.forEach(function (node) { + let element; + if (node.type === 'text') { + element = document.createTextNode(node.text); + } else { + element = document.createElement('span'); + element.classList.add('search-result-highlight'); + element.innerText = node.text; + } + documentTitle.appendChild(element); + }); + const documentHit = document.createElement('div'); + documentHit.classList.add('search-result-document-hit'); + const documentHitLink = document.createElement('a'); + documentHitLink.href = siteRootPath + doc.url + (sectionTitle ? '#' + sectionTitle.hash : ''); + documentHit.appendChild(documentHitLink); + if (highlightingResult.sectionTitleNodes.length > 0) { + const documentSectionTitle = document.createElement('div'); + documentSectionTitle.classList.add('search-result-section-title'); + documentHitLink.appendChild(documentSectionTitle); + highlightingResult.sectionTitleNodes.forEach((node) => createHighlightedText(node, documentSectionTitle)); + } + highlightingResult.pageContentNodes.forEach((node) => createHighlightedText(node, documentHitLink)); + + // only show keyword when we got a hit on them + if (doc.keyword && highlightingResult.pageKeywordNodes.length > 1) { + const documentKeywords = document.createElement('div'); + documentKeywords.classList.add('search-result-keywords'); + const documentKeywordsFieldLabel = document.createElement('span'); + documentKeywordsFieldLabel.classList.add('search-result-keywords-field-label'); + documentKeywordsFieldLabel.innerText = 'keywords: '; + const documentKeywordsList = document.createElement('span'); + documentKeywordsList.classList.add('search-result-keywords-list'); + highlightingResult.pageKeywordNodes.forEach((node) => createHighlightedText(node, documentKeywordsList)); + documentKeywords.appendChild(documentKeywordsFieldLabel); + documentKeywords.appendChild(documentKeywordsList); + documentHitLink.appendChild(documentKeywords); + } + const searchResultItem = document.createElement('div'); + searchResultItem.classList.add('search-result-item'); + searchResultItem.appendChild(documentTitle); + searchResultItem.appendChild(documentHit); + searchResultItem.addEventListener('mousedown', function (e) { + e.preventDefault(); + }); + return searchResultItem + } + + /** + * Creates an element from a highlightingResultNode and add it to the targetNode. + * @param {Object} highlightingResultNode + * @param {String} highlightingResultNode.type - type of the node + * @param {String} highlightingResultNode.text + * @param {Node} targetNode + */ + function createHighlightedText (highlightingResultNode, targetNode) { + let element; + if (highlightingResultNode.type === 'text') { + element = document.createTextNode(highlightingResultNode.text); + } else { + element = document.createElement('span'); + element.classList.add('search-result-highlight'); + element.innerText = highlightingResultNode.text; + } + targetNode.appendChild(element); + } + + function createNoResult (text) { + const searchResultItem = document.createElement('div'); + searchResultItem.classList.add('search-result-item'); + const documentHit = document.createElement('div'); + documentHit.classList.add('search-result-document-hit'); + const message = document.createElement('strong'); + message.innerText = 'No results found for query "' + text + '"'; + documentHit.appendChild(message); + searchResultItem.appendChild(documentHit); + return searchResultItem + } + + function clearSearchResults (reset) { + if (reset === true) searchInput.value = ''; + searchResultContainer.innerHTML = ''; + } + + function filter (result, documents) { + const facetFilter = facetFilterInput?.checked && facetFilterInput.dataset.facetFilter; + if (facetFilter) { + const [field, value] = facetFilter.split(':'); + return result.filter((item) => { + const ids = item.ref.split('-'); + const docId = ids[0]; + const doc = documents[docId]; + return field in doc && doc[field] === value + }) + } + return result + } + + function search (index, documents, queryString) { + // execute an exact match search + let query; + let result = filter( + index.query(function (lunrQuery) { + const parser = new globalThis.lunr.QueryParser(queryString, lunrQuery); + parser.parse(); + query = lunrQuery; + }), + documents + ); + if (result.length > 0) { + return result + } + // no result, use a begins with search + result = filter( + index.query(function (lunrQuery) { + lunrQuery.clauses = query.clauses.map((clause) => { + if (clause.presence !== globalThis.lunr.Query.presence.PROHIBITED) { + clause.term = clause.term + '*'; + clause.wildcard = globalThis.lunr.Query.wildcard.TRAILING; + clause.usePipeline = false; + } + return clause + }); + }), + documents + ); + if (result.length > 0) { + return result + } + // no result, use a contains search + result = filter( + index.query(function (lunrQuery) { + lunrQuery.clauses = query.clauses.map((clause) => { + if (clause.presence !== globalThis.lunr.Query.presence.PROHIBITED) { + clause.term = '*' + clause.term + '*'; + clause.wildcard = globalThis.lunr.Query.wildcard.LEADING | globalThis.lunr.Query.wildcard.TRAILING; + clause.usePipeline = false; + } + return clause + }); + }), + documents + ); + return result + } + + function searchIndex (index, store, text) { + clearSearchResults(false); + if (text.trim() === '') { + return + } + const result = search(index, store.documents, text); + const searchResultDataset = document.createElement('div'); + searchResultDataset.classList.add('search-result-dataset'); + searchResultContainer.appendChild(searchResultDataset); + if (result.length > 0) { + createSearchResult(result, store, searchResultDataset); + } else { + searchResultDataset.appendChild(createNoResult(text)); + } + } + + function confineEvent (e) { + e.stopPropagation(); + } + + function debounce (func, wait, immediate) { + let timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + } + } + + function enableSearchInput (enabled) { + if (facetFilterInput) { + facetFilterInput.disabled = !enabled; + } + searchInput.disabled = !enabled; + searchInput.title = enabled ? '' : 'Loading index...'; + } + + function isClosed () { + return searchResultContainer.childElementCount === 0 + } + + function executeSearch (index) { + const debug = 'URLSearchParams' in globalThis && new URLSearchParams(globalThis.location.search).has('lunr-debug'); + const query = searchInput.value; + try { + if (!query) return clearSearchResults() + searchIndex(index.index, index.store, query); + } catch (err) { + if (err instanceof globalThis.lunr.QueryParseError) { + if (debug) { + console.debug('Invalid search query: ' + query + ' (' + err.message + ')'); + } + } else { + console.error('Something went wrong while searching', err); + } + } + } + + function toggleFilter (e, index) { + searchInput.focus(); + if (!isClosed()) { + executeSearch(index); + } + } + + function initSearch (lunr, data) { + const start = performance.now(); + const index = { index: lunr.Index.load(data.index), store: data.store }; + enableSearchInput(true); + searchInput.dispatchEvent( + new CustomEvent('loadedindex', { + detail: { + took: performance.now() - start, + }, + }) + ); + searchInput.addEventListener( + 'keydown', + debounce(function (e) { + if (e.key === 'Escape' || e.key === 'Esc') return clearSearchResults(true) + executeSearch(index); + }, 100) + ); + searchInput.addEventListener('click', confineEvent); + searchResultContainer.addEventListener('click', confineEvent); + if (facetFilterInput) { + facetFilterInput.parentElement.addEventListener('click', confineEvent); + facetFilterInput.addEventListener('change', (e) => toggleFilter(e, index)); + } + document.documentElement.addEventListener('click', clearSearchResults); + } + + // disable the search input until the index is loaded + enableSearchInput(false); + + exports.initSearch = initSearch; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/build/site/_/js/site.js b/build/site/_/js/site.js new file mode 100644 index 00000000..059804bb --- /dev/null +++ b/build/site/_/js/site.js @@ -0,0 +1,7 @@ +!function(){"use strict";var e,o,r,s=/^sect(\d)$/,i=document.querySelector(".nav-container"),a=document.querySelector(".nav-toggle"),c=i.querySelector(".nav"),l=(a.addEventListener("click",function(e){if(a.classList.contains("is-active"))return u(e);v(e);var e=document.documentElement,t=(e.classList.add("is-clipped--nav"),a.classList.add("is-active"),i.classList.add("is-active"),c.getBoundingClientRect()),n=window.innerHeight-Math.round(t.top);Math.round(t.height)!==n&&(c.style.height=n+"px");e.addEventListener("click",u)}),i.addEventListener("click",v),i.querySelector("[data-panel=menu]"));function t(){var e,t,n=window.location.hash;if(n&&(n.indexOf("%")&&(n=decodeURIComponent(n)),!(e=l.querySelector('.nav-link[href="'+n+'"]')))){n=document.getElementById(n.slice(1));if(n)for(var i=n,a=document.querySelector("article.doc");(i=i.parentNode)&&i!==a;){var c=i.id;if((c=c||(c=s.test(i.className))&&(i.firstElementChild||{}).id)&&(e=l.querySelector('.nav-link[href="#'+c+'"]')))break}}if(e)t=e.parentNode;else{if(!r)return;e=(t=r).querySelector(".nav-link")}t!==o&&(h(l,".nav-item.is-active").forEach(function(e){e.classList.remove("is-active","is-current-path","is-current-page")}),t.classList.add("is-current-page"),d(o=t),p(l,e))}function d(e){for(var t,n=e.parentNode;!(t=n.classList).contains("nav-menu");)"LI"===n.tagName&&t.contains("nav-item")&&t.add("is-active","is-current-path"),n=n.parentNode;e.classList.add("is-active")}function n(){var e,t,n,i;this.classList.toggle("is-active")&&(e=parseFloat(window.getComputedStyle(this).marginTop),t=this.getBoundingClientRect(),n=l.getBoundingClientRect(),0<(i=(t.bottom-n.top-n.height+e).toFixed()))&&(l.scrollTop+=Math.min((t.top-n.top-e).toFixed(),i))}function u(e){v(e);e=document.documentElement;e.classList.remove("is-clipped--nav"),a.classList.remove("is-active"),i.classList.remove("is-active"),e.removeEventListener("click",u)}function v(e){e.stopPropagation()}function p(e,t){var n=e.getBoundingClientRect(),i=n.height,a=window.getComputedStyle(c);"sticky"===a.position&&(i-=n.top-parseFloat(a.top)),e.scrollTop=Math.max(0,.5*(t.getBoundingClientRect().height-i)+t.offsetTop)}function h(e,t){return[].slice.call(e.querySelectorAll(t))}l&&(e=i.querySelector("[data-panel=explore]"),o=l.querySelector(".is-current-page"),(r=o)?(d(o),p(l,o.querySelector(".nav-link"))):l.scrollTop=0,h(l,".nav-item-toggle").forEach(function(e){var t=e.parentElement,e=(e.addEventListener("click",n.bind(t)),function(e,t){e=e.nextElementSibling;return(!e||!t||e[e.matches?"matches":"msMatchesSelector"](t))&&e}(e,".nav-text"));e&&(e.style.cursor="pointer",e.addEventListener("click",n.bind(t)))}),e&&e.querySelector(".context").addEventListener("click",function(){h(c,"[data-panel]").forEach(function(e){e.classList.toggle("is-active")})}),l.addEventListener("mousedown",function(e){1":"")+".sect"+c);r.push("h"+(i+1)+"[id]")}else r.push("h1[id].sect0");n.push(r.join(">"))}m=n.join(","),f=d.parentNode;var a,s=[].slice.call((f||document).querySelectorAll(m));if(!s.length)return e.parentNode.removeChild(e);var l={},u=s.reduce(function(e,t){var o=document.createElement("a"),n=(o.textContent=t.textContent,l[o.href="#"+t.id]=o,document.createElement("li"));return n.dataset.level=parseInt(t.nodeName.slice(1),10)-1,n.appendChild(o),e.appendChild(n),e},document.createElement("ul")),f=e.querySelector(".toc-menu"),m=(f||((f=document.createElement("div")).className="toc-menu"),document.createElement("h3")),e=(m.textContent=e.dataset.title||"Contents",f.appendChild(m),f.appendChild(u),!document.getElementById("toc")&&d.querySelector("h1.page ~ :not(.is-before-toc)"));e&&((m=document.createElement("aside")).className="toc embedded",m.appendChild(f.cloneNode(!0)),e.parentNode.insertBefore(m,e)),window.addEventListener("load",function(){p(),window.addEventListener("scroll",p)})}}function p(){var n,i,t,e=window.pageYOffset,o=1.15*h(document.documentElement,"fontSize"),r=d.offsetTop;e&&window.innerHeight+e+2>=document.documentElement.scrollHeight?(a=Array.isArray(a)?a:Array(a||0),n=[],i=s.length-1,s.forEach(function(e,t){var o="#"+e.id;t===i||e.getBoundingClientRect().top+h(e,"paddingTop")>r?(n.push(o),a.indexOf(o)<0&&l[o].classList.add("is-active")):~a.indexOf(o)&&l[a.shift()].classList.remove("is-active")}),u.scrollTop=u.scrollHeight-u.offsetHeight,a=1r)return!0;t="#"+e.id}),t?t!==a&&(a&&l[a].classList.remove("is-active"),(e=l[t]).classList.add("is-active"),u.scrollHeight>u.offsetHeight&&(u.scrollTop=Math.max(0,e.offsetTop+e.offsetHeight-u.offsetHeight)),a=t):a&&(l[a].classList.remove("is-active"),a=void 0))}function h(e,t){return parseFloat(window.getComputedStyle(e)[t])}}(); +!function(){"use strict";var o=document.querySelector("article.doc"),t=document.querySelector(".toolbar");function i(e){return e&&(~e.indexOf("%")?decodeURIComponent(e):e).slice(1)}function r(e){if(e){if(e.altKey||e.ctrlKey)return;window.location.hash="#"+this.id,e.preventDefault()}window.scrollTo(0,function e(t,n){return o.contains(t)?e(t.offsetParent,t.offsetTop+n):n}(this,0)-t.getBoundingClientRect().bottom)}window.addEventListener("load",function e(t){var n;(n=i(window.location.hash))&&(n=document.getElementById(n))&&(r.bind(n)(),setTimeout(r.bind(n),0)),window.removeEventListener("load",e)}),Array.prototype.slice.call(document.querySelectorAll('a[href^="#"]')).forEach(function(e){var t;(t=i(e.hash))&&(t=document.getElementById(t))&&e.addEventListener("click",r.bind(t))})}(); +!function(){"use strict";var t,e=document.querySelector(".page-versions .version-menu-toggle");e&&(t=document.querySelector(".page-versions"),e.addEventListener("click",function(e){t.classList.toggle("is-active"),e.stopPropagation()}),document.documentElement.addEventListener("click",function(){t.classList.remove("is-active")}))}(); +!function(){"use strict";var t=document.querySelector(".navbar-burger");t&&t.addEventListener("click",function(t){t.stopPropagation(),document.documentElement.classList.toggle("is-clipped--navbar"),this.classList.toggle("is-active");t=document.getElementById(this.dataset.target);{var e;t.classList.toggle("is-active")&&(t.style.maxHeight="",e=window.innerHeight-Math.round(t.getBoundingClientRect().top),parseInt(window.getComputedStyle(t).maxHeight,10)!==e)&&(t.style.maxHeight=e+"px")}}.bind(t))}(); +!function(){"use strict";var o=/^\$ (\S[^\\\n]*(\\\n(?!\$ )[^\\\n]*)*)(?=\n|$)/gm,s=/( ) *\\\n *|\\\n( ?) */g,l=/ +$/gm,e=(document.getElementById("site-script")||{dataset:{}}).dataset,d=null==e.uiRootPath?".":e.uiRootPath,r=e.svgAs,p=window.navigator.clipboard;[].slice.call(document.querySelectorAll(".doc pre.highlight, .doc .literalblock pre")).forEach(function(e){var t,n,a,c;if(e.classList.contains("highlight"))(i=(t=e.querySelector("code")).dataset.lang)&&"console"!==i&&((a=document.createElement("span")).className="source-lang",a.appendChild(document.createTextNode(i)));else{if(!e.innerText.startsWith("$ "))return;var i=e.parentNode.parentNode;i.classList.remove("literalblock"),i.classList.add("listingblock"),e.classList.add("highlightjs","highlight"),(t=document.createElement("code")).className="language-console hljs",t.dataset.lang="console",t.appendChild(e.firstChild),e.appendChild(t)}(i=document.createElement("div")).className="source-toolbox",a&&i.appendChild(a),p&&((n=document.createElement("button")).className="copy-button",n.setAttribute("title","Copy to clipboard"),"svg"===r?((a=document.createElementNS("http://www.w3.org/2000/svg","svg")).setAttribute("class","copy-icon"),(c=document.createElementNS("http://www.w3.org/2000/svg","use")).setAttribute("href",d+"/img/octicons-16.svg#icon-clippy"),a.appendChild(c),n.appendChild(a)):((c=document.createElement("img")).src=d+"/img/octicons-16.svg#view-clippy",c.alt="copy icon",c.className="copy-icon",n.appendChild(c)),(a=document.createElement("span")).className="copy-toast",a.appendChild(document.createTextNode("Copied!")),n.appendChild(a),i.appendChild(n)),e.parentNode.appendChild(i),n&&n.addEventListener("click",function(e){var t=e.innerText.replace(l,"");"console"===e.dataset.lang&&t.startsWith("$ ")&&(t=function(e){var t,n=[];for(;t=o.exec(e);)n.push(t[1].replace(s,"$1$2"));return n.join(" && ")}(t));window.navigator.clipboard.writeText(t).then(function(){this.classList.add("clicked"),this.offsetHeight,this.classList.remove("clicked")}.bind(this),function(){})}.bind(n,t))})}(); +!function(){"use strict";var e;(e=document.getElementById("theme-switcher-checkbox"))&&(e.checked=document.documentElement.classList.contains("theme-dark"),e.addEventListener("change",function(){document.documentElement.classList.toggle("theme-dark",this.checked),document.documentElement.setAttribute("data-theme",this.checked?"dark":"light"),function(e){window.localStorage&&window.localStorage.setItem("theme",e)}(this.checked?"dark":"light")}.bind(e)))}(); \ No newline at end of file diff --git a/build/site/_/js/vendor/highlight.js b/build/site/_/js/vendor/highlight.js new file mode 100644 index 00000000..ecf864ba --- /dev/null +++ b/build/site/_/js/vendor/highlight.js @@ -0,0 +1 @@ +!function(){function e(e){return{aliases:["adoc"],contains:[e.COMMENT("^/{4,}\\n","\\n/{4,}$",{relevance:10}),e.COMMENT("^//","$",{relevance:0}),{className:"title",begin:"^\\.\\w.*$"},{begin:"^[=\\*]{4,}\\n",end:"\\n^[=\\*]{4,}$",relevance:10},{className:"section",relevance:10,variants:[{begin:"^(={1,5}) .+?( \\1)?$"},{begin:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{className:"meta",begin:"^:.+?:",end:"\\s",excludeEnd:!0,relevance:10},{className:"meta",begin:"^\\[.+?\\]$",relevance:0},{className:"quote",begin:"^_{4,}\\n",end:"\\n_{4,}$",relevance:10},{className:"code",begin:"^[\\-\\.]{4,}\\n",end:"\\n[\\-\\.]{4,}$",relevance:10},{begin:"^\\+{4,}\\n",end:"\\n\\+{4,}$",contains:[{begin:"<",end:">",subLanguage:"xml",relevance:0}],relevance:10},{className:"bullet",begin:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{className:"symbol",begin:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",relevance:10},{className:"strong",begin:"\\B\\*(?![\\*\\s])",end:"(\\n{2}|\\*)",contains:[{begin:"\\\\*\\w",relevance:0}]},{className:"emphasis",begin:"\\B'(?!['\\s])",end:"(\\n{2}|')",contains:[{begin:"\\\\'\\w",relevance:0}],relevance:0},{className:"emphasis",begin:"_(?![_\\s])",end:"(\\n{2}|_)",relevance:0},{className:"string",variants:[{begin:"``.+?''"},{begin:"`.+?'"}]},{className:"code",begin:"(`.+?`|\\+.+?\\+)",relevance:0},{className:"code",begin:"^[ \\t]",end:"$",relevance:0},{begin:"^'{3,}[ \\t]*$",relevance:10},{begin:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",returnBegin:!0,contains:[{begin:"(link|image:?):",relevance:0},{className:"link",begin:"\\w",end:"[^\\[]+",relevance:0},{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0,relevance:0}],relevance:10}]}}function n(e){var n={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},a={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,{className:"variable",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]}]};return{aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},e.HASH_COMMENT_MODE,a,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}function a(e){var n={className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}};return{case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,{begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[n]}]}]}}function t(e){var n="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",a={className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0};return{aliases:["jsp"],keywords:n,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:n,relevance:0,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},a,{className:"meta",begin:"@[A-Za-z]+"}]}}function i(e){var n="<>",a="",t=/<[A-Za-z0-9\\._:-]+/,i=/\/[A-Za-z0-9\\._:-]+>|\/>/,s="[A-Za-z$_][0-9A-Za-z$_]*",r={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:r,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},d={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,o],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,o]},o=(o.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,c,d,u,l,e.REGEXP_MODE],o.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]));return{aliases:["js","jsx","mjs","cjs"],keywords:r,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,c,d,u,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:s+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,l,{begin:/[{,\n]\s*/,relevance:0,contains:[{begin:s+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:s,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+s+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,contains:o}]}]},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:n,end:a},{begin:t,end:i}],subLanguage:"xml",contains:[{begin:t,end:i,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:s}),{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:o}],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor get set",end:/\{/,excludeEnd:!0}],illegal:/#(?!!)/}}function s(e){var n={literal:"true false null"},a=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],t=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],i={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:n},s={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(i,{begin:/:/})].concat(a),illegal:"\\S"},e={begin:"\\[",end:"\\]",contains:[e.inherit(i)],illegal:"\\S"};return t.push(s,e),a.forEach(function(e){t.push(e)}),{contains:t,keywords:n,illegal:"\\S"}}function r(e){return{aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$"},{begin:"^.+?\\n[=-]{2,}$"}]},{begin:"<",end:">",subLanguage:"xml",relevance:0},{className:"bullet",begin:"^\\s*([*+-]|(\\d+\\.))\\s+"},{className:"strong",begin:"[*_]{2}.+?[*_]{2}"},{className:"emphasis",variants:[{begin:"\\*.+?\\*"},{begin:"_.+?_",relevance:0}]},{className:"quote",begin:"^>\\s+",end:"$"},{className:"code",variants:[{begin:"^```\\w*\\s*$",end:"^```[ ]*$"},{begin:"`.+?`"},{begin:"^( {4}|\\t)",end:"$",relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},{begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}function l(e){return{disableAutodetect:!0}}function o(e){var n="[ \\t\\f]*",a="("+n+"[:=]"+n+"|[ \\t\\f]+)",t="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",i="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:a,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:t+a,returnBegin:!0,contains:[{className:"attr",begin:t,endsParent:!0,relevance:0}],starts:s},{begin:i+a,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:i,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:i+n+"$"}]}}function c(e){var n=e.COMMENT("--","$");return{case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,n,e.HASH_COMMENT_MODE]}}function d(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},t=e.inherit(a,{begin:"\\(",end:"\\)"}),i=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),s=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),r={endsWithParent:!0,illegal:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,s,i,t,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,t,s,i]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{begin:/<\?(php)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[r],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[r],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["actionscript","javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},r]}]}}function u(e){var n="true false yes no null",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]};return{case_insensitive:!0,aliases:["yml","YAML","yaml"],contains:[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!"+e.UNDERSCORE_IDENT_RE},{className:"type",begin:"!!"+e.UNDERSCORE_IDENT_RE},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:e.C_NUMBER_RE+"\\b"},a]}}var g,m,_={};g=function(t){var a,u=[],s=Object.keys,w=Object.create(null),r=Object.create(null),O=!0,n=/^(no-?highlight|plain|text)$/i,l=/\blang(?:uage)?-([\w-]+)\b/i,i=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,x="",M="Could not find the language '{}', did you forget to load/include a language module?",R={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},o="of and for in not or if then".split(" ");function k(e){return e.replace(/&/g,"&").replace(//g,">")}function g(e){return e.nodeName.toLowerCase()}function c(e){return n.test(e)}function d(e){var n,a={},t=Array.prototype.slice.call(arguments,1);for(n in e)a[n]=e[n];return t.forEach(function(e){for(n in e)a[n]=e[n]}),a}function m(e){var i=[];return function e(n,a){for(var t=n.firstChild;t;t=t.nextSibling)3===t.nodeType?a+=t.nodeValue.length:1===t.nodeType&&(i.push({event:"start",offset:a,node:t}),a=e(t,a),g(t).match(/br|hr|img|input/)||i.push({event:"stop",offset:a,node:t}));return a}(e,0),i}function _(e,n,a){var t=0,i="",s=[];function r(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function o(e){i+=""}function c(e){("start"===e.event?l:o)(e.node)}for(;e.length||n.length;){var d=r();if(i+=k(a.substring(t,d[0].offset)),t=d[0].offset,d===e){for(s.reverse().forEach(o);c(d.splice(0,1)[0]),(d=r())===e&&d.length&&d[0].offset===t;);s.reverse().forEach(l)}else"start"===d[0].event?s.push(d[0].node):s.pop(),c(d.splice(0,1)[0])}return i+k(a.substr(t))}function b(n){return n.variants&&!n.cached_variants&&(n.cached_variants=n.variants.map(function(e){return d(n,{variants:null},e)})),n.cached_variants||(function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(n)?[d(n,{starts:n.starts?d(n.starts):null})]:Object.isFrozen(n)?[d(n)]:[n])}function p(e){if(a&&!e.langApiRestored){for(var n in e.langApiRestored=!0,a)e[n]&&(e[a[n]]=e[n]);(e.contains||[]).concat(e.variants||[]).forEach(p)}}function f(n,t){var i={};return"string"==typeof n?a("keyword",n):s(n).forEach(function(e){a(e,n[e])}),i;function a(a,e){(e=t?e.toLowerCase():e).split(" ").forEach(function(e){var n,e=e.split("|");i[e[0]]=[a,(n=e[0],(e=e[1])?Number(e):function(e){return-1!=o.indexOf(e.toLowerCase())}(n)?0:1)]})}}function C(t){function d(e){return e&&e.source||e}function u(e,n){return new RegExp(d(e),"m"+(t.case_insensitive?"i":"")+(n?"g":""))}function i(i){var s={},r=[],l={},a=1;function e(e,n){s[a]=e,r.push([e,n]),a+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var n=0;n')+n+(a?"":x)):n:""}function r(){var e,n,a,t,i;if(!_.keywords)return k(v);for(a="",_.lexemesRe.lastIndex=e=0,n=_.lexemesRe.exec(v);n;)a+=k(v.substring(e,n.index)),t=_,i=n,i=m.case_insensitive?i[0].toLowerCase():i[0],(t=t.keywords.hasOwnProperty(i)&&t.keywords[i])?(E+=t[1],a+=s(t[0],k(n[0]))):a+=k(n[0]),e=_.lexemesRe.lastIndex,n=_.lexemesRe.exec(v);return a+k(v.substr(e))}function l(){var e,n;p+=null!=_.subLanguage?(n="string"==typeof _.subLanguage)&&!w[_.subLanguage]?k(v):(e=n?S(_.subLanguage,v,!0,b[_.subLanguage]):A(v,_.subLanguage.length?_.subLanguage:void 0),0<_.relevance&&(E+=e.relevance),n&&(b[_.subLanguage]=e.top),s(e.language,e.value,!1,!0)):r(),v=""}function o(e){p+=e.className?s(e.className,"",!0):"",_=Object.create(e,{parent:{value:_}})}function c(e){var n=e[0],e=e.rule;return e&&e.endSameAsBegin&&(e.endRe=new RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),e.skip?v+=n:(e.excludeBegin&&(v+=n),l(),e.returnBegin||e.excludeBegin||(v=n)),o(e),e.returnBegin?0:n.length}function d(e){var n=e[0],e=i.substr(e.index),a=function e(n,a){if(t=n.endRe,i=a,(t=t&&t.exec(i))&&0===t.index){for(;n.endsParent&&n.parent;)n=n.parent;return n}var t,i;if(n.endsWithParent)return e(n.parent,a)}(_,e);if(a){e=_;for(e.skip?v+=n:(e.returnEnd||e.excludeEnd||(v+=n),l(),e.excludeEnd&&(v=n));_.className&&(p+=x),_.skip||_.subLanguage||(E+=_.relevance),(_=_.parent)!==a.parent;);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),o(a.starts)),e.returnEnd?0:n.length}}var u={};function g(e,n){var a=n&&n[0];if(v+=e,null==a)return l(),0;if("begin"==u.type&&"end"==n.type&&u.index==n.index&&""===a)return v+=i.slice(n.index,n.index+1),1;if("illegal"===u.type&&""===a)return v+=i.slice(n.index,n.index+1),1;if("begin"===(u=n).type)return c(n);if("illegal"===n.type&&!t)throw new Error('Illegal lexeme "'+a+'" for mode "'+(_.className||"")+'"');if("end"===n.type){e=d(n);if(null!=e)return e}return v+=a,a.length}var m=T(n);if(!m)throw console.error(M.replace("{}",n)),new Error('Unknown language: "'+n+'"');C(m);for(var _=a||m,b={},p="",f=_;f!==m;f=f.parent)f.className&&(p=s(f.className,"",!0)+p);var v="",E=0;try{for(var h,y,N=0;;){if(_.terminators.lastIndex=N,!(h=_.terminators.exec(i)))break;y=g(i.substring(N,h.index),h),N=h.index+y}for(g(i.substr(N)),f=_;f.parent;f=f.parent)f.className&&(p+=x);return{relevance:E,value:p,illegal:!1,language:n,top:_}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{illegal:!0,relevance:0,value:k(i)};if(O)return{relevance:0,value:k(i),language:n,top:_,errorRaised:e};throw e}}function A(a,e){e=e||R.languages||s(w);var t={relevance:0,value:k(a)},i=t;return e.filter(T).filter(N).forEach(function(e){var n=S(e,a,!1);n.language=e,n.relevance>i.relevance&&(i=n),n.relevance>t.relevance&&(i=t,t=n)}),i.language&&(t.second_best=i),t}function v(e){return R.tabReplace||R.useBR?e.replace(i,function(e,n){return R.useBR&&"\n"===e?"
":R.tabReplace?n.replace(/\t/g,R.tabReplace):""}):e}function E(e){var n,a,t,i,s=function(e){var n,a,t,i,s,r=e.className+" ";if(r+=e.parentNode?e.parentNode.className:"",a=l.exec(r))return(s=T(a[1]))||(console.warn(M.replace("{}",a[1])),console.warn("Falling back to no-highlight mode for this block.",e)),s?a[1]:"no-highlight";for(n=0,t=(r=r.split(/\s+/)).length;n/g,"\n"):a=e,i=a.textContent,n=s?S(s,i,!0):A(i),(a=m(a)).length&&((t=document.createElement("div")).innerHTML=n.value,n.value=_(a,m(t),i)),n.value=v(n.value),e.innerHTML=n.value,e.className=(a=e.className,t=s,i=n.language,t=t?r[t]:i,i=[a.trim()],a.match(/\bhljs\b/)||i.push("hljs"),-1===a.indexOf(t)&&i.push(t),i.join(" ").trim()),e.result={language:n.language,re:n.relevance},n.second_best&&(e.second_best={language:n.second_best.language,re:n.second_best.relevance}))}function h(){var e;h.called||(h.called=!0,e=document.querySelectorAll("pre code"),u.forEach.call(e,E))}var y={disableAutodetect:!0};function T(e){return e=(e||"").toLowerCase(),w[e]||w[r[e]]}function N(e){e=T(e);return e&&!e.disableAutodetect}return t.highlight=S,t.highlightAuto=A,t.fixMarkup=v,t.highlightBlock=E,t.configure=function(e){R=d(R,e)},t.initHighlighting=h,t.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},t.registerLanguage=function(n,e){var a;try{a=e(t)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!O)throw e;console.error(e),a=y}p(w[n]=a),a.rawDefinition=e.bind(null,t),a.aliases&&a.aliases.forEach(function(e){r[e]=n})},t.listLanguages=function(){return s(w)},t.getLanguage=T,t.requireLanguage=function(e){var n=T(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},t.autoDetection=N,t.inherit=d,t.debugMode=function(){O=!1},t.IDENT_RE="[a-zA-Z]\\w*",t.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",t.NUMBER_RE="\\b\\d+(\\.\\d+)?",t.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",t.BINARY_NUMBER_RE="\\b(0b[01]+)",t.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",t.BACKSLASH_ESCAPE={begin:"\\\\[\\s\\S]",relevance:0},t.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[t.BACKSLASH_ESCAPE]},t.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[t.BACKSLASH_ESCAPE]},t.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},t.COMMENT=function(e,n,a){e=t.inherit({className:"comment",begin:e,end:n,contains:[]},a||{});return e.contains.push(t.PHRASAL_WORDS_MODE),e.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),e},t.C_LINE_COMMENT_MODE=t.COMMENT("//","$"),t.C_BLOCK_COMMENT_MODE=t.COMMENT("/\\*","\\*/"),t.HASH_COMMENT_MODE=t.COMMENT("#","$"),t.NUMBER_MODE={className:"number",begin:t.NUMBER_RE,relevance:0},t.C_NUMBER_MODE={className:"number",begin:t.C_NUMBER_RE,relevance:0},t.BINARY_NUMBER_MODE={className:"number",begin:t.BINARY_NUMBER_RE,relevance:0},t.CSS_NUMBER_MODE={className:"number",begin:t.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},t.REGEXP_MODE={className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[t.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[t.BACKSLASH_ESCAPE]}]},t.TITLE_MODE={className:"title",begin:t.IDENT_RE,relevance:0},t.UNDERSCORE_TITLE_MODE={className:"title",begin:t.UNDERSCORE_IDENT_RE,relevance:0},t.METHOD_GUARD={begin:"\\.\\s*"+t.UNDERSCORE_IDENT_RE,relevance:0},[t.BACKSLASH_ESCAPE,t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,t.PHRASAL_WORDS_MODE,t.COMMENT,t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.HASH_COMMENT_MODE,t.NUMBER_MODE,t.C_NUMBER_MODE,t.BINARY_NUMBER_MODE,t.CSS_NUMBER_MODE,t.REGEXP_MODE,t.TITLE_MODE,t.UNDERSCORE_TITLE_MODE,t.METHOD_GUARD].forEach(function(e){!function n(a){Object.freeze(a);var t="function"==typeof a;Object.getOwnPropertyNames(a).forEach(function(e){!a.hasOwnProperty(e)||null===a[e]||"object"!=typeof a[e]&&"function"!=typeof a[e]||t&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(a[e])||n(a[e])});return a}(e)}),t},m="object"==typeof window&&window||"object"==typeof self&&self,void 0===_||_.nodeType?m&&(m.hljs=g({}),"function"==typeof define)&&define.amd&&define([],function(){return m.hljs}):g(_);!function(){"use strict";_.registerLanguage("asciidoc",e),_.registerLanguage("bash",n),_.registerLanguage("css",a),_.registerLanguage("java",t),_.registerLanguage("javascript",i),_.registerLanguage("json",s),_.registerLanguage("markdown",r),_.registerLanguage("none",l),_.registerLanguage("properties",o),_.registerLanguage("sql",c),_.registerLanguage("xml",d),_.registerLanguage("yaml",u),[].slice.call(document.querySelectorAll("pre code.hljs[data-lang]")).forEach(function(e){_.highlightBlock(e)})}()}(); \ No newline at end of file diff --git a/build/site/_/js/vendor/lunr.js b/build/site/_/js/vendor/lunr.js new file mode 100644 index 00000000..cdc94cd3 --- /dev/null +++ b/build/site/_/js/vendor/lunr.js @@ -0,0 +1,6 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ +!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.3.9",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/build/site/favicon.ico b/build/site/favicon.ico new file mode 100644 index 00000000..1557db19 Binary files /dev/null and b/build/site/favicon.ico differ diff --git a/build/site/index.html b/build/site/index.html new file mode 100644 index 00000000..a92d29c9 --- /dev/null +++ b/build/site/index.html @@ -0,0 +1,9 @@ + + + + + + +Redirect Notice +

Redirect Notice

+

The page you requested has been relocated to https://microprofile.io/microprofile-tutorial/6.1/index.html.

diff --git a/build/site/microprofile-tutorial/6.1/README.html b/build/site/microprofile-tutorial/6.1/README.html new file mode 100644 index 00000000..14657cda --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/README.html @@ -0,0 +1,191 @@ + + + + + +README :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

README

+
+

Overview

+
+
+

microprofile samples

+
+
+

This repo contains the source files that are used to build the MicroProfile Tutorial. The source files are authored in AsciiDoc. AsciiDoc is similar to markdown but is particularly suited for user documentation. The source files are processed and integrated into the MicroProfile Tutorial site using Antora, which is a tool for building documentation sites.

+
+
+
+
+

Running the Site Locally

+
+
+

After building the site, you can serve it locally using Python’s built-in HTTP server:

+
+
+
+
cd build/site
+python3 -m http.server 8080
+
+
+
+

Then open your browser and navigate to http://localhost:8080 to view the site.

+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/_images/favicon.png b/build/site/microprofile-tutorial/6.1/_images/favicon.png new file mode 100644 index 00000000..53ada59e --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/_images/favicon.png @@ -0,0 +1,7 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ + diff --git a/build/site/microprofile-tutorial/6.1/_images/figure1-2.png b/build/site/microprofile-tutorial/6.1/_images/figure1-2.png new file mode 100644 index 00000000..a9d1a072 Binary files /dev/null and b/build/site/microprofile-tutorial/6.1/_images/figure1-2.png differ diff --git a/build/site/microprofile-tutorial/6.1/_images/figure4-1.png b/build/site/microprofile-tutorial/6.1/_images/figure4-1.png new file mode 100644 index 00000000..86d4b0e3 Binary files /dev/null and b/build/site/microprofile-tutorial/6.1/_images/figure4-1.png differ diff --git a/build/site/microprofile-tutorial/6.1/_images/figure8-1.png b/build/site/microprofile-tutorial/6.1/_images/figure8-1.png new file mode 100644 index 00000000..122a31d2 Binary files /dev/null and b/build/site/microprofile-tutorial/6.1/_images/figure8-1.png differ diff --git a/build/site/microprofile-tutorial/6.1/_images/figureFM-1.png b/build/site/microprofile-tutorial/6.1/_images/figureFM-1.png new file mode 100644 index 00000000..934b5a46 Binary files /dev/null and b/build/site/microprofile-tutorial/6.1/_images/figureFM-1.png differ diff --git a/build/site/microprofile-tutorial/6.1/chapter01/chapter01.html b/build/site/microprofile-tutorial/6.1/chapter01/chapter01.html new file mode 100644 index 00000000..c3e3c04c --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter01/chapter01.html @@ -0,0 +1,846 @@ + + + + + +Introduction to MicroProfile :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

Introduction to MicroProfile

+
+

Introduction

+
+
+

This introductory chapter provides a comprehensive overview of the MicroProfile platform, setting the stage for subsequent chapters in this tutorial. It aims to familiarize you with the fundamentals of the MicroProfile platform, its need, and benefits. Finally, we will discuss its place in the broader context of enterprise Java development.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    What is MicroProfile

    +
  • +
  • +

    Need for MicroProfile

    +
  • +
  • +

    MicroProfile Specifications

    +
  • +
  • +

    Current MicroProfile Implementations

    +
  • +
  • +

    Architecture Philosophy

    +
  • +
  • +

    Benefits of MicroProfile

    +
  • +
  • +

    Relationship with Jakarta EE specification

    +
  • +
+
+
+
+
+

What is MicroProfile

+
+
+

MicroProfile is an open-source specification that enhances enterprise Java technologies for microservices development. It provides a set of APIs and specifications for building modern, scalable, resilient, and efficient microservices-based applications. The primary goal of MicroProfile is to simplify the development for Java developers, enabling them to create applications optimized for cloud-native-development.

+
+
+

MicroProfile was initiated in June 2016 by a collaboration of industry leaders, Java community members, and individual contributors. In the following year, the project was transitioned to the Eclipse Foundation to enhance the project’s openness and vendor-neutral stance. Now, MicroProfile has become a key framework for extending Java in the cloud-computing domain, offering a comprehensive suite of APIs tailored for developing microservices in a cloud-native ecosystem.

+
+
+

The MicroProfile Working Group currently comprises of the following members:

+
+
+

Committer Representative (Year 2024)

+
+
+
    +
  • +

    Emerson Castañeda

    +
  • +
+
+
+

Java user groups

+
+
+
    +
  • +

    Atlanta Java User Group (AJUG)

    +
  • +
  • +

    Association of the German Java User Groups (iJUG)

    +
  • +
+
+
+

Corporate Members

+
+
+
    +
  • +

    IBM

    +
  • +
  • +

    Fujitsu

    +
  • +
  • +

    Red Hat

    +
  • +
  • +

    Primeton

    +
  • +
  • +

    Payara

    +
  • +
  • +

    Microsoft

    +
  • +
  • +

    Tomitribe

    +
  • +
  • +

    Oracle

    +
  • +
+
+
+

This collective effort demonstrates MicroProfile’s commitment to evolving Java enterprise development for the modern cloud environment, leveraging the expertise of its community.

+
+
+
+
+

Need for MicroProfile

+
+
+

The MicroProfile Specification was developed to address the following requirements:

+
+
+
    +
  • +

    Microservices Architecture Adoption: The industry shift towards microservices architecture has brought several advantages, including improved flexibility, scalability, and speed of deployment. However, it also introduced several new challenges for developers due to the added complexities. These include ensuring seamless integration between microservices, securing each microservice individually as well as interactions between them, managing performance and efficiency, designing microservices to be fault-tolerant and resilient to failures, ensuring data consistency across services, managing configurations across multiple environments and managing various independently deployable components. To address these challenges, MicroProfile provides a simplified and optimized set of APIs designed to build and deploy Java-based microservices applications.

    +
  • +
  • +

    Limitations of Traditional Enterprise Java: Traditional enterprise Java frameworks, like Java EE (now Jakarta EE), were often seen as too monolithic and heavyweight for microservices while evolving too slowly. It led to a demand for a more streamlined and microservices-focused framework. MicroProfile fills this gap by providing a lightweight alternative optimized for microservices development.

    +
  • +
  • +

    Cloud-Native Application Development: The rise of cloud-native applications necessitated new features such as external configuration, health checks, and fault tolerance, which existing Java standards did not adequately address. MicroProfile bridges these gaps left, making it easier for developers to create resilient, scalable, and manageable microservices for cloud-native application development using Java.

    +
  • +
  • +

    Community-Driven Innovation: The rapid pace of technological change in microservices necessitated a collaborative platform for innovation. MicroProfile, backed by community and vendor support, promotes rapid evolution to meet these demands.

    +
  • +
  • +

    Vendor Neutrality and Interoperability: There was a need for a framework that could provide standardization across different implementations and environments, ensuring compatibility and avoiding vendor lock-in.

    +
  • +
  • +

    Focus on Simplicity and Productivity: Developers needed a simple, easy-to-understand framework that increased productivity by reducing boilerplate code and focusing on essential microservice functionalities. Well-defined standards and patterns eliminate the need to reinvent the wheel, allowing developers to focus on microservices logic.

    +
  • +
  • +

    Support for familiar programming model: MicroProfile was founded with support for Jakarta JSON Processing, Jakarta JSON Binding, Jakarta RESTful Web Services, and Jakarta Contexts and Dependency Injection (CDI) to define the core programming model and accelerate adoption.

    +
  • +
  • +

    Lightweight and Resilient Services: With the microservices architecture, there’s a need for frameworks that support the development of lightweight, resilient, and independently deployable services, which are essential for microservices.

    +
  • +
  • +

    Rapid Adaptation to New Trends: The technology landscape, especially around microservices, is constantly evolving. A framework like MicroProfile, which is community-driven and rapidly evolving, can adapt quickly to these changes, continually incorporating new practices and technologies, including:

    +
    +
      +
    • +

      Streaming APIs and Reactive Programming Model: To facilitate non-blocking communication and data processing, enhancing system responsiveness and scalability.

      +
    • +
    • +

      API-First Development (Open API): Emphasizing the design and documentation of microservices with an API-first approach, promoting interoperability and clear service contracts.

      +
    • +
    • +

      Eventual Consistency and Long Running Actions (LRA): Addressing the challenges of data consistency in distributed systems without compromising system performance.

      +
    • +
    +
    +
  • +
  • +

    Enhanced Observability and Monitoring: Microservices architectures complicate application monitoring and observability. A framework with built-in support for these capabilities simplifies the management of distributed services.

    +
  • +
+
+
+
+
+

MicroProfile Specifications

+
+
+

MicroProfile specifications are divided into two main categories: Platform and Standalone.

+
+
+
+MicroProfile 6.1 +
+
Figure 1. MicroProfile Specifications
+
+
+

MicroProfile Platform Component Specifications

+
+

The MicroProfile Platform Specification is the core set of MicroProfile specifications designed to provide the foundational functionalities needed for microservices development. These specifications solve specific microservices challenges, including configuration, fault tolerance, health checks, metrics, and security. The table below provides a list of platform specifications of MicroProfile along with their descriptions:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpecificationDescription

Config

Provides an easy-to-use and flexible system for application configuration.

Fault Tolerance

Implements patterns like Circuit Breaker, Bulkhead, Retry, Timeout, and Fallback for building resilient applications.

JWT Authentication

Defines a standard for using OpenID Connect (OIDC) based JSON Web Tokens(JWT) for role-based access control(RBAC) of microservices endpoints for secure communication.

Metrics

Define custom application metrics and expose platform metrics on a standard endpoint using a standard format to external monitoring systems.

Health

Allows applications to expose their health and readiness to perform operations to the underlying platform, which is crucial for automated recovery in cloud environments.

Open API

Facilitates the generation of OpenAPI documentation for RESTful services, making API discovery and understanding easier.

Telemetry

Provides a unified set of APIs, libraries, and tools for collecting, processing, and exporting telemetry data (metrics, traces, and logs) from cloud-native applications and services.

Rest Client

Defines a type-safe approach to invoke RESTful services over HTTP(S), simplifying the development of Rest clients.

Jakarta EE Core Profile 10

An optimized Jakarta EE platform designed specifically for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint.

+
+
+

Standalone (Outside Umbrella) Specifications

+
+

Standalone specifications address more advanced needs that every microservices application may not require. They allow for innovation and experimentation in areas that are evolving or where there’s a need to address niche concerns without burdening the core platform with additional complexity. The table below provides a list of standalone specifications of MicroProfile along with their descriptions:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpecificationDescription

Context Propagation

Defines a way to propagate context between threads and managed executor services. Ensure that the context is consistent during executing asynchronous tasks or across different services.

GraphQL

Provides a layer on top of Jakarta EE that allows the creation of GraphQL services. This specification makes it easier to build APIs, enabling clients to request exactly the data they need and nothing more.

Long Running Actions (LRA)

Focuses on providing a model for developing services that participate in long-running processes, ensuring consistency and reliability without necessarily locking data.

Reactive Messaging

Aims to facilitate building applications that communicate via reactive streams, enabling the development of event-driven, responsive, and resilient microservices.

Reactive Streams Operators

Provides a way to process data streams in a reactive manner, allowing for non-blocking system design and improving the efficiency of data processing in microservices.

Open Tracing

Integrates distributed tracing by defining a way for services to trace requests across service boundaries, improving observability.

+
+
+
+
+

MicroProfile Implementations

+
+
+

Below is the list of MicroProfile Implementations, each offering a platform for building and running microservices-based applications:

+
+
+ +
+
+
+
+

Architecture Philosophy

+
+
+

The overall goal of MicroProfile architecture is to provide a lightweight enterprise-grade framework tailored for building cloud-native applications and enabling developers to build and deploy microservices with Java easily:

+
+
+
    +
  • +

    Simplicity: MicroProfile APIs are designed to be simple and easy to use. They avoid unnecessary complexity and focus on providing the essential functionality for building microservices.

    +
  • +
  • +

    Modularity: Its modular approach allows developers to use only what they need, reducing the overhead typically associated with enterprise frameworks.

    +
  • +
  • +

    Standards-based: MicroProfile is based on open standards and specifications, ensuring compatibility and consistency across different implementations.

    +
  • +
  • +

    Community-driven: It encourages active participation from the Java community for continuous evolution.

    +
  • +
  • +

    Vendor-Neutral: MicroProfile is vendor-neutral. It’s supported by several industry players, ensuring that no single company controls its direction.

    +
  • +
  • +

    Focus on Cloud-Native Applications: The architecture is specifically tailored for cloud environments. MicroProfile integrates with a number of cloud-native technologies, such as Kubernetes and Istio. This makes it easy to deploy and manage MicroProfile applications in cloud environments.

    +
  • +
  • +

    Reactive programming: MicroProfile supports reactive programming, which is a style of programming that is well-suited for building microservices. Reactive applications are responsive and scalable, and they can handle high volumes of concurrent requests.

    +
  • +
+
+
+
+MicroProfile Architecture Philosophy +
+
Figure 2. Architecture Philosophy of MicroProfile
+
+
+

Benefits of MicroProfile

+
+

MicroProfile offers several benefits, making it a compelling choice for developing microservices, especially in Java-centric environments. These benefits include:

+
+
+
    +
  • +

    Optimized for Microservices: MicroProfile is designed explicitly for creating microservices, offering APIs that cater to the unique challenges of this architectural style.

    +
  • +
  • +

    Cloud-Native Focus: The framework includes features such as externalized configuration, health checks, and metrics, which are essential for building and operating cloud-native applications effectively. MicroProfile is inherently designed for cloud-native applications.

    +
  • +
  • +

    Open Source and Standards-Based: As an open-source framework based on open standards, MicroProfile facilitates interoperability and reduces the risk of vendor lock-in.

    +
  • +
  • +

    Enhanced Productivity, Rapid Development and Deployment: MicroProfile simplifies microservices development with a set of standard APIs. With its focus on simplicity and productivity, MicroProfile helps speed up the development and deployment of microservices by providing essential functionalities and reducing boilerplate code.

    +
  • +
  • +

    Community-Driven Innovation: Being community-driven, MicroProfile evolves quickly, incorporating new trends and best practices in microservices development. MicroProfile is backed by a strong Java community, ensuring continuous improvement and support.

    +
  • +
  • +

    Vendor Neutrality: Being vendor-neutral, MicroProfile is supported by a wide range of industry players, which ensures a broad choice of tools and platforms for developers.

    +
  • +
  • +

    Compatibility with Jakarta EE: MicroProfile is complementary to Jakarta EE, whether using MicroProfile implementations that support a small subset of Jakarta EE (such as Core Profile) or implementations that extend the full Jakarta EE Platform implementations with MicroProfile.

    +
  • +
  • +

    Lightweight and Modular: It provides a lightweight model compared to traditional enterprise Java frameworks. Its modularity allows developers to use only the necessary components, reducing the application’s footprint and overhead.

    +
  • +
  • +

    Scalability: The framework supports the development of scalable applications, essential for microservices that handle varying loads efficiently.

    +
  • +
  • +

    Enhanced Resilience: MicroProfile includes specifications for fault tolerance patterns like retries, circuit breakers, timeouts, and bulkheads, which are crucial for building resilient services that can withstand network and service failures.

    +
  • +
  • +

    Security Features: MicroProfile’s JWT Authentication provides a standardized way to secure microservices, making it easier to implement authentication and authorization.

    +
  • +
  • +

    Ease of Testing: With its lightweight nature and support for advanced features like Rest Client, MicroProfile simplifies the testing of microservices, both in isolation and in integration scenarios.

    +
  • +
+
+
+
+
+
+

Relationship with Jakarta EE specification

+
+
+

Jakarta EE is an open specification with more than 40 component specifications to address a wide array of needs of enterprise Java development. MicroProfile complements this by providing a baseline platform definition that optimizes enterprise Java for microservices architecture and delivers application portability across multiple compatible runtimes. Many Jakarta EE implementations that target a broad array of applications supplement Jakarta EE with MicroProfile to better support microservices. Their coexistence allows developers to harness the strength of both platforms, thereby facilitating a more versatile and adaptive approach to modern enterprise and cloud-native application development. MicroProfile strategically leverages Java EE developers' existing skill sets, enabling them to transition and adapt to microservices development with minimal learning curve. This ensures that developers can easily design and implement microservices architecture, enhancing productivity and facilitating the creation of cloud-native applications. Later in this tutorial, we will explore how MicroProfile API extends Jakarta EE’s capability to address microservices-specific challenges.

+
+
+ + + + + +
+ + +MicroProfile and Jakarta EE are complementary technologies. Both platforms enable developers to stay at the forefront of cloud-native application development. +
+
+
+
+
+

Conclusion

+
+
+

In this section, we explored the MicroProfile platform in detail, laying the foundation for understanding how it revolutionizes the development of microservices using Java. We started by defining MicroProfile, emphasizing its role as an open-source specification tailored for microservices development. Key contributions from industry leaders and community members have positioned MicroProfile as a pivotal technology in the Java ecosystem, especially for cloud-native application development. We delved into the essential specifications of MicroProfile, each playing a critical role in addressing specific challenges in microservices development, from configuration management to service resilience. As we move forward in this tutorial, we will delve deeper into each specification and discover how to implement MicroProfile in real-world Java applications effectively.

+
+
+
+
+

Glossary

+
+
+
+
Microservices
+
+

An architectural style for building applications as a collection of small, independent services. Each service focuses on a specific business capability and communicates with other services through well-defined APIs.

+
+
+
+
+
+
APIs (Application Programming Interfaces)
+
+

A set of definitions and protocols that specify how software components interact with each other.

+
+
+
+
+
+
Cloud-native development
+
+

An approach to building and running applications that are specifically designed for the cloud environment. It involves using technologies and practices that leverage the benefits of cloud platforms, such as scalability, elasticity, and pay-as-you-go pricing.

+
+
+
+
+
+
Eclipse Foundation Working Group
+
+

A collaborative group of industry leaders and Java community members who actively contribute to the of development of Eclipse projects like MicroProfile within the Eclipse Foundation framework.

+
+
+
+
+
+
Jakarta EE
+
+

Jakarta EE (formerly Java Platform, Enterprise Edition, or Java EE) is a set of specifications, extending Java Platform, Standard Edition, or Java SE with specifications for enterprise features such as web services, database persistence, asynchronous messaging and more.

+
+
+
+
+
+
External Configuration
+
+

A technique in application development where configuration data is separated from the application code, allowing the application’s behavior to be adjusted without changing the code, especially useful in cloud-native and microservices architectures.

+
+
+
+
+
+
Health Checks
+
+

Mechanisms used in microservices architectures to continuously check the status of an application or service to ensure it is functioning correctly and available to users.

+
+
+
+
+
+
Fault Tolerance
+
+

The ability of a system to continue operating in the event of the failure of some of its components. This feature is critical for maintaining high availability and reliability in microservices architectures.

+
+
+
+
+
+
Vendor Neutrality
+
+

The principle of designing software products and standards not controlled by any single vendor, promoting user interoperability and choice.

+
+
+
+
+
+
Interoperability
+
+

The ability of a software to exchange and make use of information across different platforms and services.

+
+
+
+
+
+
JSON-P (JSON Processing)
+
+

A Jakarta EE (formerly Java EE) API that enables parsing, generating, transforming, and querying JSON data. It facilitates the processing of JSON data within the Java programming environment. Currently it is known as Jakarta JSON Processing.

+
+
+
+
+
+
JSON-B (JSON Binding)
+
+

A Jakarta EE (formerly Java EE) API for binding Java objects to JSON messages and vice versa, streamlining the serialization and deserialization process. It allows custom mappings to handle complex conversion scenarios efficiently. Currently it is known as Jakarta JSON Binding.

+
+
+
+
+
+
JAX-RS (Java API for RESTful Web Services)
+
+

A Jakarta EE API for creating web services according to the REST architectural pattern in Java, using annotations to simplify development. It enables the easy creation and management of resources via standard HTTP methods. It is currently known as Jakarta RESTful Web Services.

+
+
+
+
+
+
CDI (Contexts and Dependency Injection)
+
+

A Jakarta EE API for enterprise-grade dependency injection, offering type-safe mechanisms, context lifecycle management, and a framework for decoupling application components. It enhances modularity and facilitates the development of loosely coupled, easily testable applications.

+
+
+
+
+
+
Boilerplate Code
+
+

A piece of code that must be included in many places with little or no alteration.

+
+
+
+
+
+
Lightweight Services
+
+

Services designed to consume minimal computing resources, enhancing performance and efficiency, particularly relevant in a microservices architecture.

+
+
+
+
+
+
Resilient Services
+
+

Services built to recover quickly from failures and continue operating. It is critical for maintaining the reliability of microservices-based applications.

+
+
+
+
+
+
Observability
+
+

The ability to measure the internal state of a system by examining its outputs, crucial for understanding the performance and behavior of microservices.

+
+
+
+
+
+
Monitoring
+
+

The practice of tracking and logging the performance and status of applications and infrastructure, essential for maintaining system health in microservices environments.

+
+
+
+
+
+
Circuit Breaker
+
+

A fault tolerance mechanism that prevents a failure in one service from causing system-wide failure, by temporarily disabling failing services.

+
+
+
+
+
+
Bulkhead
+
+

A pattern that isolates failures in one part of a system from the others, ensuring that parts of an application can continue functioning despite issues elsewhere.

+
+
+
+
+
+
Retry
+
+

A simple fault tolerance mechanism where an operation is attempted again if it fails initially, based on predefined criteria.

+
+
+
+
+
+
Timeout
+
+

A mechanism to limit the time waiting for a response from a service, helping to avoid resource deadlock situations in distributed systems.

+
+
+
+
+
+
Fallback
+
+

A fault tolerance mechanism that provides an alternative solution or response when a primary method fails.

+
+
+
+
+
+
Role-Based Access Control (RBAC)
+
+

A method of restricting system access to authorized users based on their roles within an organization.

+
+
+
+
+
+
Kubernetes
+
+

An open-source platform for automating deployment, scaling, and operations of application containers across clusters of hosts.

+
+
+
+
+
+
Istio
+
+

An open platform to connect, manage, and secure microservices, providing an easy way to create a network of deployed services with load balancing, service-to-service authentication, and monitoring.

+
+
+
+
+
+
Reactive Programming
+
+

A programming paradigm oriented around data flows and the propagation of change, enabling the development of responsive and resilient systems.

+
+
+
+
+
+
Distributed Tracing
+
+

A method for monitoring applications, especially those built using a microservices architecture, by tracking the flow of requests and responses across services.

+
+
+
+
+
+
Long Running Actions (LRA)
+
+

A model for managing long-duration, distributed transactions across microservices without locking resources.

+
+
+
+
+
+
Reactive Streams
+
+

An initiative to provide a standard for asynchronous stream processing with non-blocking back pressure.

+
+
+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-00.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-00.html new file mode 100644 index 00000000..10c15cdc --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-00.html @@ -0,0 +1,569 @@ + + + + + +Getting Started with MicroProfile :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

Getting Started with MicroProfile

+
+

Introduction

+
+
+

In this chapter, you’ll embark on your MicroProfile journey! We will guide you through creating your first microservice, equipping you with the essential understanding to leverage this robust framework for building modern, cloud-native applications. This journey begins with setting up your development environment, then diving into creating a microservice.

+
+
+
+
+

Topics Covered

+
+
+
    +
  • +

    Development Environment Setup

    +
  • +
  • +

    Configuring Build Tools

    +
  • +
  • +

    Initializing a New MicroProfile Project

    +
  • +
  • +

    Choosing Right Modules for your Application

    +
  • +
  • +

    Building a RESTful service

    +
  • +
  • +

    Deployment

    +
  • +
  • +

    Testing your microservices

    +
  • +
  • +

    Exploring Further with MicroProfile

    +
  • +
+
+
+
+
+

Development Environment Setup

+
+
+

Let’s begin by preparing your workspace for MicroProfile development:

+
+
+

Java Development Kit (JDK)

+
+

MicroProfile, a Java framework, runs on the Java Virtual Machine (JVM), making the Java Development Kit (JDK) an essential component of your development environment.

+
+
+

To install JDK, follow the steps below:

+
+
+
    +
  1. +

    Download: Visit the official OpenJDK website and download the JDK version compatible with your operating system.

    +
  2. +
  3. +

    Install: Follow the installation instructions provided on this official OpenJDK Installation guide.

    +
  4. +
  5. +

    Verify: After Installation, run the following command in your command line or terminal to verify the Installation:

    +
  6. +
+
+
+
+
java -version
+
+
+
+

You should an output similar to the one below:

+
+
+
+
openjdk 17.0.10 2024-01-16 LTS
+OpenJDK Runtime Environment Microsoft-8902769 (build 17.0.10+7-LTS)
+OpenJDK 64-Bit Server VM Microsoft-8902769 (build 17.0.10+7-LTS, mixed mode,
+sharing)
+
+
+
+

This confirms that JDK 17 has been successfully installed on your system.

+
+
+ + + + + +
+ + +For most MicroProfile implementations, JDK 11 or later is recommended. In +this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK +providers such as Oracle JDK, Amazon Corretto, Azul Zulu, OpenJ9 also offer +compatible JDK distributions. +
+
+
+
+

Build Tools (Maven or Gradle)

+
+

Build tools like Apache Maven or Gradle are commonly used for managing project dependencies and building Java applications. You can install the one that best fits your project needs. Here’s a brief overview to help you decide:

+
+
+
    +
  • +

    Apache Maven: Known for its convention-over-configuration approach, Maven is a popular choice due to its simple project setup and extensive plugin repository.

    +
  • +
  • +

    Gradle: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It’s a great choice for complex projects requiring customization.

    +
  • +
+
+
+ + + + + +
+ + +If your existing project’s build uses Maven wrapper (mvnw) or Gradle wrapper (gradlew), you don’t have to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. +
+
+
+

Installing Apache Maven

+
+

To install Maven follow the steps below:

+
+
+
    +
  1. +

    Visit the Installing Apache Maven web page to download the latest version.

    +
  2. +
  3. +

    Follow the installation instructions provided on the site.

    +
  4. +
  5. +

    Verify the Maven installation by running this command in your terminal or command line.

    +
  6. +
+
+
+
+
mvn -v
+
+
+
+

You should see output similar to:

+
+
+
+
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
+Maven home: /usr/local/sdkman/candidates/maven/current
+Java version: 17.0.10, vendor: Microsoft, runtime: /usr/lib/jvm/msopenjdk-current
+Default locale: en_US, platform encoding: UTF-8
+OS name: "linux", version: "6.2.0-1019-azure", arch: "amd64", family: "unix"
+
+
+
+

After Maven is installed, you can configure the pom.xml file in your project to include the MicroProfile dependencies.

+
+
+
+

Gradle

+
+

To install Gradle follow the step below:

+
+
+
    +
  1. +

    Visit the Gradle | Installation web page to download the latest version.

    +
  2. +
  3. +

    Follow the installation instructions provided on the site.

    +
  4. +
  5. +

    Verify the installation by running this command in your terminal or command line.

    +
  6. +
+
+
+
+
gradle -version
+
+
+
+

You should see output similar to:

+
+
+
+
Welcome to Gradle 8.6!
+
+Here are the highlights of this release:
+ - Configurable encryption key for configuration cache
+ - Build init improvements
+ - Build authoring improvements
+
+For more details see https://docs.gradle.org/8.6/release-notes.html
+
+------------------------------------------------------------
+Gradle 8.6
+------------------------------------------------------------
+
+Build time:   2024-02-02 16:47:16 UTC
+Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e
+
+Kotlin:       1.9.20
+Groovy:       3.0.17
+Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
+JVM:          17.0.10 (Microsoft 17.0.10+7-LTS)
+OS:           Linux 6.2.0-1019-azure amd64
+
+
+
+

After Gradle is installed, you can configure the build.gradle file in your project to include the MicroProfile dependencies.

+
+
+

Whether you opt for Maven’s stability and convention or Gradle’s flexibility and performance, understanding how to configure and use your chosen build tool is important for MicroProfile development.

+
+
+
+
+

Integrated Development Environments

+
+

Integrated Development Environments (IDEs) enhance developer productivity by providing a rich set of features and extensions such as project boostraping, dependency management, intelligent code completion, configuration assistance, test runners, build, hot deployment and debugging tools. For MicroProfile development, the choice of IDE can significantly affect your development speed and efficiency. Below is a list of popular IDEs and their key features related to Java and MicroProfile development:

+
+
+

Eclipse for Enterprise Java and Web Developers

+
+

Overview: Eclipse for Enterprise Java and Web Developers is a widely used IDE for Java development, offering extensive support for Java EE, Jakarta EE, and MicroProfile, among other technologies.

+
+
+

Getting Started: The official Eclipse documentation containing instructions about creating Java projects - Creating your first Java Project

+
+
+
+

IntelliJ IDEA

+
+

Overview: IntelliJ IDEA by JetBrains supports a wide range of programming languages and frameworks, including Java, Kotlin, and frameworks like Spring, Jakarta EE, and MicroProfile.

+
+
+

Getting Started: Refer to this IntelliJ IDEA guide on Creating a Java Project Using IntelliJ IDEA 2024.1.

+
+
+
+

Apache NetBeans

+
+

Overview: NetBeans is an open-source IDE that supports Java development, including Java SE, Java EE, JavaFX, and more.

+
+
+

Getting Started: Check out this NetBeans Java Quick Start Tutorial for a tutorial on creating a Java application.

+
+
+
+

Visual Studio Code

+
+

Overview: Visual Studio Code is a lightweight, powerful source code editor that supports Java development through extensions.

+
+
+

Getting Started: To start with Java in VS Code, follow this Getting Started with Java in VS Code documentation.

+
+
+

Selecting an IDE should be based on personal preference, as the best choice varies depending on individual needs, familiarity, and the specific features that enhance your productivity. Each IDE offers unique advantages for MicroProfile development.

+
+
+
+
+

Setting up MicroProfile Runtime

+
+

MicroProfile applications need a runtime that supports MicroProfile specifications or a MicroProfile-compatible server to run your applications. Below are some popular options, each with unique features tailored to different needs:

+
+
+

Open Liberty

+
+

Open Liberty is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease. Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance.

+
+
+

Downloading Open Liberty page provides access to its latest releases and documentation to help you set up your environment.

+
+
+
+

Quarkus

+
+

Quarkus is known for its container-first approach, offering fast startup times and low memory footprint. It aims to optimize Java for Kubernetes and cloud environments

+
+
+

This Getting Started with Quarkus page will guide you through creating your first Quartus project and exploring its cloud-native capabilities.

+
+
+
+

Payara Micro

+
+

Payara Micro is a lightweight middleware platform suited for containerized Jakarta EE and MicroProfile applications.

+
+
+

The Payara Platform Community Edition enables easy packaging of applications into a single, runnable JAR file, simplifying deployment and scaling in cloud environments. This site about Payara Platform Community Edition offers downloads and documentation to get started.

+
+
+
+

WildFly

+
+

WildFly is a flexible, lightweight, managed application runtime that offers full Jakarta EE and MicroProfile support. WildFly is designed for scalability and flexibility in both traditional and cloud-native environments.

+
+
+

WildFly Downloads page offers the latest versions and documentation to get you started.

+
+
+
+

Helidon

+
+

Developed by Oracle, Helidon MP implements MicroProfile specifications. It provides a familiar programming model for Jakarta EE developers and enables efficient microservice development.

+
+
+

Helidon Documentation provides comprehensive resources to help developers get started with the framework, understand its core concepts, and develop microservices efficiently.

+
+
+
+

Apache TomEE

+
+

Apache TomEE integrates several Apache projects with Apache Tomcat to provide a Jakarta EE environment. It offers support for MicroProfile, allowing developers to build and deploy microservices using the well-known Jakarta EE technologies with additional MicroProfile capabilities.

+
+
+

TomEE Downloads and TomEE MicroProfile Documentation page provide the necessary resources to get started with TomEE for MicroProfile development.

+
+
+
+
+

MicroProfile Starter

+
+

To kickstart your MicroProfile project, use the MicroProfile Starter to generate a sample project with your chosen server and specifications. This tool provides a customizable project structure and generates necessary boilerplate code and configuration.

+
+
+
    +
  • +

    Visit the MicroProfile Starter page - the official website for generating the MicroProfile project templates.

    +
  • +
  • +

    Provide a groupId for your project, it’s an identifier for your project and should be unique to avoid conflicts with other libraries or projects.

    +
  • +
+
+
+ + + + + +
+ + +Its recommended convention is to start your groupId with the reverse domain name of your organization (for example, io.microprofile). +
+
+
+
    +
  • +

    Enter the 'artifactID', which is the name of your project (e.g., 'mp-ecomm-store').

    +
  • +
  • +

    Select the Java SE version your project will use.

    +
  • +
  • +

    Select the MicroProfile version you want to use. Ideally, you should choose the latest version for the most up-to-date features but also consider the runtime’s support.

    +
  • +
  • +

    Select the specifications you want to include in your project. These could be Config, Fault Tolerance, JWT Auth, Metrics, Health, Open API, Open Tracing, Rest Client. Choose what is relevant to your application.

    +
  • +
  • +

    Click the Download button.

    +
  • +
  • +

    Unzip the generated project and import it into your IDE.

    +
  • +
+
+
+

This completes the development environment setup. Now we are all set to begin development using MicroProfile.

+
+
+ + + + + +
+ + +At the time of writing this tutorial, the latest MicroProfile released version was 6.1. The MicroProfile Starter does not currently support this version. Hence, we will not be using MicroProfile Starter to generate the project structure. +
+
+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-01.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-01.html new file mode 100644 index 00000000..c483a020 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-01.html @@ -0,0 +1,368 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Creating a Java Project for MicroProfile Development

+
+
+

Using Your IDE

+
+

Most modern IDEs have built-in support for creating Java and Maven projects. Depending on your chosen IDE, look for options like "New Project", or "New Maven Project" to get started. These options typically guide you through the setup process, including specifying the project’s groupId, artifactId, and dependencies.

+
+
+
+

Using Maven from Command Line (Terminal)

+
+

For developers who prefer using the command line or for those setting up projects in environments without an IDE, Maven can generate the base structure of a Java project through its archetype mechanism.

+
+
+

To create a project using Maven, open your terminal or command line and run the following command:

+
+
+
+
mvn archetype:generate -DgroupId=io.microprofile.tutorial -DartifactId=store
+-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
+
+
+
+

The above command creates a new Maven project based on the maven-archetype-quickstart archetype, setting the groupId as io.microprofile.tutorial and the artifactID as store.

+
+
+

Explanation:

+
+
+
    +
  • +

    mvn archetype:generate goal in this command initializes the project creation process and instructs Maven to generate a new project based on an archetype template.

    +
  • +
  • +

    -DgroupId=io.microprofile.tutorial Specifies the groupId for the project. The groupId is a unique identifier for your project and is usually based on the domain name of your organization in reverse. In this case, io.microprofile.tutorial represent a project related to MicroProfile tutorial.

    +
  • +
  • +

    -DartifactId=store: Sets the artifactId for the project. The artifactId is the name of the project and is used as the base name for the project’s artifacts (e.g., WAR files). Here, store is used as the project name to indicate this project is related an e-commerce store application.

    +
  • +
  • +

    -DarchetypeArtifactId=maven-archetype-quickstart: Indicates which archetype to use for generating the project. The maven-archetype-quickstart is a basic template for a Java application, providing a simple project structure that includes a sample application (App.java) and a unit test (AppTest.java).

    +
  • +
  • +

    -DinteractiveMode=false: This option disables interactive mode, meaning Maven will not prompt you for input during the project generation process. All necessary information is provided via the command-line options, allowing the command to execute without further user interaction.

    +
  • +
+
+
+

Next, using your file explorer or command line, create the following folder structure:

+
+
+
+
.
+├── pom.xml
+├── README.adoc
+└── src
+    └── main
+    │   └── java
+    │       └── io
+    │           └── microprofile
+    │               └── tutorial
+    │                  └── store
+    │                      └── product
+    │                      │    ├── entity
+    │                      │    │   └── Product.java
+    │	                   │    └── resource
+    │                      │       └── ProductResource.java
+    │                      └── ProductRestApplication.java
+    └── test
+	└── java
+            └── io
+                └── microprofile
+                    └── tutorial
+                       └── store
+			   └── product
+                               └── ProductServiceTest.java
+
+
+
+

The standard location for your Java source code is this folder:

+
+
+
+
src/main/java
+
+
+
+

And, the standard location for your test code is this folder:

+
+
+
+
src/test/java
+
+
+
+

You can delete App.java and AppTest.java files from the poject as these are not required in MicroProfile development.

+
+
+

The heart of your Maven project is pom.xml (Project Object Model) file. It defines project metadata, dependencies, build configurations and plugins.

+
+
+

The content for the pom.xml file should look as below, ensure MicroProfile depencency is added:

+
+
+
+
<?xml version="1.0" encoding="UTF-8"?>
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>io.microprofile.tutorial</groupId>
+  <artifactId>mp-ecomm</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>war</packaging>
+
+  <!-- Setting the source and target of the Java Compiler !>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+    <!-- Setting the source and target of the Java Compiler -->
+    <maven.compiler.source>17</maven.compiler.source>
+    <maven.compiler.target>17</maven.compiler.target>
+...
+...
+   <!-- Add Lombok dependency -->
+   <dependency>
+     <groupId>org.projectlombok</groupId>
+     <artifactId>lombok</artifactId>
+     <version>1.18.26</version>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- Adding Jakarta EE dependencies -->
+   <dependency>
+     <groupId>jakarta.platform</groupId>
+     <artifactId>jakarta.jakartaee-api</artifactId>
+     <version>10.0.0</version>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- Adding MicroProfie dependency -->
+   <dependency>
+     <groupId>org.eclipse.microprofile</groupId>
+     <artifactId>microprofile</artifactId>
+     <version>6.1</version>
+     <type>pom</type>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- JUnit Jupiter API for writing tests -->
+   <dependency>
+       <groupId>org.junit.jupiter</groupId>
+       <artifactId>junit-jupiter-api</artifactId>
+       <version>5.8.2</version>
+       <scope>test</scope>
+   </dependency>
+
+   <!-- JUnit Jupiter Engine for running tests -->
+   <dependency>
+       <groupId>org.junit.jupiter</groupId>
+       <artifactId>junit-jupiter-engine</artifactId>
+       <version>5.8.2</version>
+       <scope>test</scope>
+   </dependency>
+...
+
+
+
+

Below is the list of essential dependencies you need to add to your Maven pom.xml for a MicroProfile project:

+
+
+
    +
  • +

    Lombok Dependency - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code.

    +
  • +
  • +

    Jakarta EE API Dependency - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications.

    +
  • +
  • +

    MicroProfile Dependency - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project.

    +
  • +
  • +

    JUnit Jupiter API for Writing Tests - Essential for writing unit tests for your MicroProfile services.

    +
  • +
  • +

    JUnit Jupiter Engine for Running Tests - Enables the execution of JUnit tests.

    +
  • +
+
+
+

These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​.

+
+
+ + + + + +
+ + +Execute the $ mvn validate command. This checks the pom.xml file for correctness, ensuring that all necessary configuration is present and valid. +
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-02.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-02.html new file mode 100644 index 00000000..dd7cd56f --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-02.html @@ -0,0 +1,407 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Choosing Right Modules for Your MicroProfile Application

+
+
+

Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements.

+
+
+

Before diving into MicroProfile modules, it’s essential to have a clear understanding of your application’s requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it’s important to understand which ones best fit your project’s needs.

+
+
+

This section aims to help you make informed decisions about which MicroProfile modules to use.

+
+
+

Use the Entire MicroProfile Dependency

+
+

If you’re beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently.

+
+
+

For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications.

+
+
+

Maven

+
+
+
+
<!-- MicroProfile 6.1 API -->
+<dependency>
+    <groupId>org.eclipse.microprofile</groupId>
+    <artifactId>microprofile</artifactId>
+    <version>6.1</version>
+    <type>pom</type>
+    <scope>provided</scope>
+</dependency>
+
+
+
+

Gradle

+
+
+
+
dependencies {
+ compileOnly 'org.eclipse.microprofile:microprofile:6.1'
+}
+
+
+
+
+

Use Individual MicroProfile Specification Dependencies

+
+

For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application’s footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time.

+
+
+

To prevent potential conflicts or security issues associated with unused dependencies, it’s prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities.

+
+
+

The list below is provided to help you select the appropriate modules for your MicroProfile application:

+
+
+
    +
  • +

    MicroProfile Config provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.config</groupId>
+  <artifactId>microprofile-config-api</artifactId>
+  <version>3.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Health allows you to define health endpoints easily. If you’re deploying your application in a environment where the service needs to report its health status.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.health</groupId>
+  <artifactId>microprofile-health-api</artifactId>
+  <version>4.0.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Metrics offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.metrics</groupId>
+  <artifactId>microprofile-metrics-api</artifactId>
+  <version>5.1.0</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Fault Tolerance helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it.

    +
  • +
+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.fault-tolerance</groupId>
+  <artifactId>microprofile-fault-tolerance-api</artifactId>
+  <version>4.0.2</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile JWT Authentication provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services.

    +
  • +
+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.jwt</groupId>
+  <artifactId>microprofile-jwt-auth-api</artifactId>
+  <version>2.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile OpenAPI offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.openapi</groupId>
+  <artifactId>microprofile-openapi-api</artifactId>
+  <version>3.1.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Rest Client simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.rest.client</groupId>
+  <artifactId>microprofile-rest-client-api</artifactId>
+  <version>3.0</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Telemetry integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor.

    +
  • +
+
+
+

Maven

+
+
+
+
<project>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>io.opentelemetry</groupId>
+        <artifactId>opentelemetry-bom</artifactId>
+        <version>1.29.0</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>io.opentelemetry</groupId>
+      <artifactId>opentelemetry-api</artifactId>
+    </dependency>
+  </dependencies>
+</project>
+
+
+
+
    +
  • +

    Jakarta EE Core Profile dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependencies>
+    <!-- Jakarta EE 10 Core Profile -->
+    <dependency>
+        <groupId>jakarta.platform</groupId>
+        <artifactId>jakarta.jakartaee-api</artifactId>
+        <version>10.0.0</version>
+        <scope>provided</scope>
+    </dependency>
+</dependencies>
+
+
+
+

For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions.

+
+
+

When choosing MicroProfile modules, start with the minimal set that meets your application’s core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment.

+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-03.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-03.html new file mode 100644 index 00000000..1b54b3a9 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-03.html @@ -0,0 +1,609 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Developing a RESTful Web Service

+
+
+

Web Services are very popular nowadays because they allow for building decoupled systems +– services can communicate with each other without the knowledge of each other’s implementation details. +There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) +architecture. A Jakarta RESTful Webservice is a web service that uses the Representational State Transfer (REST) architecture. +This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that +all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web +applications.

+
+
+

To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, +the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as +resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. +With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) +requests this URL, the server would return a list of products in JSON format.

+
+
+

Creating an Entity class

+
+

An Entity class represents a specific object, in our case a product. It contains the product’s details id and name, +and other properties like price, and quantity. To implement an entity class first, you need to create a Product class, as below:

+
+
+
+
// Product.java
+package io.microprofile.tutorial.store.product.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Product {
+   private Long id;
+   private String name;
+   private String description;
+   private Double price;
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The Product class is a Plain Old Java Object (POJO). It has an id, name, description and price property. The id property is of type Long, The name and description properties are of type String. The price property is of type Double.

    +
  • +
  • +

    @Data annotation will generate constructors, getters, and setters for all fields. By doing this, +you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of Object type as well. +Jackson cannot work with primitive types because they cannot be null.

    +
  • +
  • +

    @AllArgsConstructor generates a constructor with one argument for each field in the class. +This is useful for instantiating objects with all their fields initialized.

    +
  • +
  • +

    @NoArgsConstructor generates a default constructor +for the class.

    +
  • +
+
+
+
+

Creating a Resource class

+
+

A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving +(CRUD) operations on the resources. Let us now create a ProductResource class with a getProducts() method to return a list of +Product objects.

+
+
+
+
// ProductResource.java
+package io.microprofile.tutorial.store.product.resource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/products")
+@ApplicationScoped
+public class ProductResource {
+
+   private List<Product> products = new ArrayList<>();
+
+   public ProductResource() {
+       // Initialize the list with some sample products
+       products.add(new Product(Long.valueOf(1L), "iPhone", "Apple iPhone 15", Double.valueOf(999.99)));
+       products.add(new Product(Long.valueOf(2L), "MacBook", "Apple MacBook Air", Double.valueOf(1299.99)));
+   }
+
+   @GET
+   @Produces(MediaType.APPLICATION_JSON)
+   public List<Product> getProducts() {
+       return products;
+   }
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The ProductResource is annotated with @ApplicationScoped. This will ensure that only one instance of this class available when the +application is running.

    +
  • +
  • +

    The ProductResource class has a getProducts() method, which returns a list of products. This method is annotated with the @GET annotation, which maps this method to the GET HTTP method.

    +
  • +
  • +

    The @Produces annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a GET request to the /api/products endpoint.

    +
  • +
  • +

    RESTful web services can produce and consume many different media types, including JSON, XML, and HTML.

    +
  • +
  • +

    Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with +@Produces(MediaType.APPLICATION_JSON) it can produce JSON.

    +
  • +
+
+
+
+

Creating an Applicaiton class

+
+

Create a class named ProductRestApplication as per the code below:

+
+
+
+
// ProductRestApplication.java
+package io.microprofile.tutorial.store.product;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+@ApplicationPath("/api")
+public class ProductRestApplication extends Application{
+
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The annotation @ApplicationPath("/api") specifies that any RESTful resources registered within this application will be accessed +under the base path /api. For example, if you have a resource class named ProductResource mapped to the path /products, it would be accessible at /api/products.

    +
  • +
+
+
+
+

Building Your Application

+
+

You may build the application using the following commands from your project’s root directory:

+
+
+
+
$ mvn compile
+
+
+
+

The above command will compile your project’s source code.

+
+
+
+
$ mvn test
+
+
+
+

The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed.

+
+
+
+
$mvn package
+
+
+
+

The above command will create a deployment package.

+
+
+
+

Deploying your microservices

+
+

This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations:

+
+
+

General Considerations:

+
+
    +
  • +

    Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project.

    +
  • +
  • +

    Packaging: Decide on a packaging format (e.g., WAR file, Docker image).

    +
  • +
  • +

    Configuration: Review and adjust any runtime configuration necessary for your service.

    +
  • +
  • +

    Deployment Tools: Leverage runtime-specific tools or commands for deployment.

    +
  • +
+
+
+
+

Deployment Options

+
+

You can then deploy this application on a MicroProfile compatible server and access the web service at +http://localhost:<port>/<contextRoot>/api/products. Replace <port> with the port number on which the web server or +application server is listening. The <contextRoot> is a placeholder for the context root of the web application. +The context root is part of the URL path that identifies the base path for the application on the web server.

+
+
+

Below are the steps for popular options. Specific steps will depend on your chosen runtime.

+
+
+

Open Liberty

+
+
+

Package your application as a WAR file using Maven or Gradle by adding the packaging tag in pom.xml.

+
+
+
+
<groupId>io.microprofile.tutorial</groupId>
+<artifactId>mp-ecomm-store</artifactId>
+<version>1.0-SNAPSHOT</version>
+<packaging>war</packaging>
+
+
+
+

Add a server configuration file at the location /main/liberty/config/server.xml with the content as below:

+
+
+
+
<server description="MicroProfile Tutorial Liberty Server">
+    <featureManager>
+        <feature>restfulWS-3.1</feature>
+        <feature>jsonb-3.0</feature>
+    </featureManager>
+
+    <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
+                  id="defaultHttpEndpoint" host="*" />
+    <webApplication location="mp-ecomm-store.war" contextRoot="${app.context.root}"/>
+</server>
+
+
+
+

Add the Open Liberty configuration in the pom.xml as below:

+
+
+
+
<properties>
+   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+   <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+   <maven.compiler.source>17</maven.compiler.source>
+   <maven.compiler.target>17</maven.compiler.target>
+   <!-- Liberty configuration -->
+   <liberty.var.default.http.port>9080</liberty.var.default.http.port>
+   <liberty.var.default.https.port>9443</liberty.var.default.https.port>
+   <liberty.var.app.context.root>mp-ecomm-store</liberty.var.app.context.root>
+ </properties>
+
+
+
+

Add the Open Liberty build plugin in the pom.xml as below:

+
+
+
+
<build>
+    <finalName>${project.artifactId}</finalName>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-war-plugin</artifactId>
+            <version>3.3.2</version>
+        </plugin>
+        <plugin>
+            <groupId>io.openliberty.tools</groupId>
+            <artifactId>liberty-maven-plugin</artifactId>
+            <version>3.8.2</version>
+            <configuration>
+                <serverName>productServer</serverName>
+            </configuration>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <version>3.0.0</version>
+            <configuration>
+                <systemPropertyVariables>
+                    <http.port>${liberty.var.default.http.port}</http.port>
+                    <war.name>${liberty.var.app.context.root}</war.name>
+                </systemPropertyVariables>
+            </configuration>
+        </plugin>
+   </plugins>
+</build>
+
+
+
+
+
+

Running Your Application

+
+

Refer to your runtime’s documentation for instructions on running your MicroProfile application. +For example, Consult the Open Liberty documentation for detailed instructions: +MicroProfile - Open Liberty Docs +Finally, use the following command from the command line or terminal to run the application on Liberty server.

+
+
+
+
$ mvn liberty:run
+
+
+
+

You can also run the following command to start the liberty server in development mode.

+
+
+
+
$ mvn liberty:dev
+
+
+
+

Assuming your server is running on http://localhost:9080/, you can access your service at: +http://localhost:9080/mp-ecomm-store/api/products.

+
+
+

To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. +Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. +The response should be:

+
+
+
+
[{"description":"Apple iPhone 15","id":1,"name":"iPhone","price":999.99},{"description":"Apple MacBook Air","id":2,"name":"MacBook","price":1299.99}]
+
+
+
+

This uses an in-memory list; In a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter.

+
+
+

Quarkus

+
+
+
    +
  • +

    Build your application as a native executable or Docker image.

    +
  • +
  • +

    Run the generated executable or deploy the Docker image to a container platform.

    +
  • +
  • +

    Refer to the Quarkus documentation for deployment guides: Creating your first application - Quarkus

    +
  • +
+
+
+

Payara Micro

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR to a Payara Micro server instance.

    +
  • +
  • +

    See the Payara Micro documentation for specific instructions: Getting Started with Payara Micro

    +
  • +
+
+
+

WildFly

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR to a WildFly server instance.

    +
  • +
  • +

    Refer to the WildFly documentation for deployment details: WildFly Developer Guide

    +
  • +
+
+
+

Helidon

+
+
+
    +
  • +

    Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging).

    +
  • +
  • +

    Build your application using Gradle.

    +
  • +
  • +

    Follow the relevant Helidon documentation for deployment steps: Helidon - Getting Started

    +
  • +
+
+
+

TomEE

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR file to the TomEE server instance.

    +
  • +
  • +

    Refer to the TomEE documentation for instructions: Serverless TomEE MicroProfile

    +
  • +
+
+
+

Additional Considerations:

+
+
    +
  • +

    Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability.

    +
  • +
  • +

    Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP.

    +
  • +
+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-04.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-04.html new file mode 100644 index 00000000..8e894d3d --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-04.html @@ -0,0 +1,294 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Testing Your Microservice

+
+
+

Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. +To create tests for your microservice, start by creating a class called ProductResourceTest, which contains unit tests for the ProductResource class as below:

+
+
+
+
// ProductResourceTest.java
+package io.microprofile.tutorial.store.product.resource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+
+public class ProductResourceTest {
+  private ProductResource productResource;
+
+   @BeforeEach
+   void setUp() {
+       productResource = new ProductResource();
+   }
+
+   @AfterEach
+   void tearDown() {
+       productResource = null;
+   }
+
+   @Test
+   void testGetProducts() {
+       List<Product> products = productResource.getProducts();
+
+       assertNotNull(products);
+       assertEquals(2, products.size());
+   }
+}
+
+
+
+

Explanation:

+
+
+

Below are the assertions to test the getProducts() method of ProductService: +* The assertNotNull(products); assertion checks that products are not null. It ensures the method returns a list, even if it’s empty. +* The assertEquals(2, products.size()); assertion verifies that the list contains two products.

+
+
+

Running Unit Tests with Maven

+
+

To run the unit tests defined in ProductResourceTest, follow these steps:

+
+
+
    +
  • +

    Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations.

    +
  • +
  • +

    Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests:

    +
  • +
+
+
+
+
$ mvn test
+
+
+
+

This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results.

+
+
+
    +
  • +

    Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions:

    +
  • +
  • +

    Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly.

    +
  • +
  • +

    Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected.

    +
  • +
+
+
+

By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment.

+
+
+
+
+
+

Next Steps

+
+
+

Now that you have a basic MicroProfile service, consider exploring further:

+
+
+
    +
  • +

    Adding configuration with MicroProfile Config

    +
  • +
  • +

    Implementing health checks using MicroProfile Health

    +
  • +
  • +

    Enhancing your service with MicroProfile Fault Tolerance

    +
  • +
+
+
+

Resources

+
+ +
+
+

After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture.

+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-05.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-05.html new file mode 100644 index 00000000..0f44cc72 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-05.html @@ -0,0 +1,281 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Package Structure

+
+
+

The Table below provides an overview of the package structure and their purposes within a typical Java-based +microservices architecture.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageDescription

dto

Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data.

entity

Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA.

repository

Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository).

service

Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService).

resource

REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource).

common

This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes.

client

For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services.

config

Configuration classes for MicroProfile Config.

exception

Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException).

util

Helper and utility classes.

+
+

Base Package: io.microprofile.tutorial.store

+
+
+
+
io.microprofile.tutorial.store
+├── catalog
+│    ├── resource
+│    ├── config
+│    ├── exception
+│    ├── entity
+│    ├── repository
+│    ├── service
+│    └── util
+├── cart
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── client
+│    ├── exception
+│    └── util
+├── user
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── inventory
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── order
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── payment
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+└── shipment
+     ├── resource
+     ├── entity
+     ├── service
+     ├── repository
+     ├── exception
+     └── util
+----
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02-06.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-06.html new file mode 100644 index 00000000..664b80d9 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02-06.html @@ -0,0 +1,195 @@ + + + + + +Untitled :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+

Glossary

+
+
+
+
Java Development Kit (JDK)
+
+

A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development.

+
+
Integrated Development Environment (IDE)
+
+

A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code.

+
+
RESTful Service
+
+

A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet.

+
+
Runtime Environment
+
+

The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables.

+
+
JUnit
+
+

A unit testing framework for Java, used to write and run repeatable tests.

+
+
Containerization
+
+

A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment.

+
+
Cloud Deployment
+
+

Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities.

+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter02/chapter02.html b/build/site/microprofile-tutorial/6.1/chapter02/chapter02.html new file mode 100644 index 00000000..85dc17b7 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter02/chapter02.html @@ -0,0 +1,1746 @@ + + + + + +Getting Started with MicroProfile :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

Getting Started with MicroProfile

+
+

Introduction

+
+
+

In this chapter, you’ll embark on your MicroProfile journey! We will guide you through creating your first microservice, equipping you with the essential understanding to leverage this robust framework for building modern, cloud-native applications. This journey begins with setting up your development environment, then diving into creating a microservice.

+
+
+
+
+

Topics Covered

+
+
+
    +
  • +

    Development Environment Setup

    +
  • +
  • +

    Configuring Build Tools

    +
  • +
  • +

    Initializing a New MicroProfile Project

    +
  • +
  • +

    Choosing Right Modules for your Application

    +
  • +
  • +

    Building a RESTful service

    +
  • +
  • +

    Deployment

    +
  • +
  • +

    Testing your microservices

    +
  • +
  • +

    Exploring Further with MicroProfile

    +
  • +
+
+
+
+
+

Development Environment Setup

+
+
+

Let’s begin by preparing your workspace for MicroProfile development:

+
+
+

Java Development Kit (JDK)

+
+

MicroProfile, a Java framework, runs on the Java Virtual Machine (JVM), making the Java Development Kit (JDK) an essential component of your development environment.

+
+
+

To install JDK, follow the steps below:

+
+
+
    +
  1. +

    Download: Visit the official OpenJDK website and download the JDK version compatible with your operating system.

    +
  2. +
  3. +

    Install: Follow the installation instructions provided on this official OpenJDK Installation guide.

    +
  4. +
  5. +

    Verify: After Installation, run the following command in your command line or terminal to verify the Installation:

    +
  6. +
+
+
+
+
java -version
+
+
+
+

You should an output similar to the one below:

+
+
+
+
openjdk 17.0.10 2024-01-16 LTS
+OpenJDK Runtime Environment Microsoft-8902769 (build 17.0.10+7-LTS)
+OpenJDK 64-Bit Server VM Microsoft-8902769 (build 17.0.10+7-LTS, mixed mode,
+sharing)
+
+
+
+

This confirms that JDK 17 has been successfully installed on your system.

+
+
+ + + + + +
+ + +For most MicroProfile implementations, JDK 11 or later is recommended. In +this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK +providers such as Oracle JDK, Amazon Corretto, Azul Zulu, OpenJ9 also offer +compatible JDK distributions. +
+
+
+
+

Build Tools (Maven or Gradle)

+
+

Build tools like Apache Maven or Gradle are commonly used for managing project dependencies and building Java applications. You can install the one that best fits your project needs. Here’s a brief overview to help you decide:

+
+
+
    +
  • +

    Apache Maven: Known for its convention-over-configuration approach, Maven is a popular choice due to its simple project setup and extensive plugin repository.

    +
  • +
  • +

    Gradle: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It’s a great choice for complex projects requiring customization.

    +
  • +
+
+
+ + + + + +
+ + +If your existing project’s build uses Maven wrapper (mvnw) or Gradle wrapper (gradlew), you don’t have to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. +
+
+
+

Installing Apache Maven

+
+

To install Maven follow the steps below:

+
+
+
    +
  1. +

    Visit the Installing Apache Maven web page to download the latest version.

    +
  2. +
  3. +

    Follow the installation instructions provided on the site.

    +
  4. +
  5. +

    Verify the Maven installation by running this command in your terminal or command line.

    +
  6. +
+
+
+
+
mvn -v
+
+
+
+

You should see output similar to:

+
+
+
+
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
+Maven home: /usr/local/sdkman/candidates/maven/current
+Java version: 17.0.10, vendor: Microsoft, runtime: /usr/lib/jvm/msopenjdk-current
+Default locale: en_US, platform encoding: UTF-8
+OS name: "linux", version: "6.2.0-1019-azure", arch: "amd64", family: "unix"
+
+
+
+

After Maven is installed, you can configure the pom.xml file in your project to include the MicroProfile dependencies.

+
+
+
+

Gradle

+
+

To install Gradle follow the step below:

+
+
+
    +
  1. +

    Visit the Gradle | Installation web page to download the latest version.

    +
  2. +
  3. +

    Follow the installation instructions provided on the site.

    +
  4. +
  5. +

    Verify the installation by running this command in your terminal or command line.

    +
  6. +
+
+
+
+
gradle -version
+
+
+
+

You should see output similar to:

+
+
+
+
Welcome to Gradle 8.6!
+
+Here are the highlights of this release:
+ - Configurable encryption key for configuration cache
+ - Build init improvements
+ - Build authoring improvements
+
+For more details see https://docs.gradle.org/8.6/release-notes.html
+
+------------------------------------------------------------
+Gradle 8.6
+------------------------------------------------------------
+
+Build time:   2024-02-02 16:47:16 UTC
+Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e
+
+Kotlin:       1.9.20
+Groovy:       3.0.17
+Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
+JVM:          17.0.10 (Microsoft 17.0.10+7-LTS)
+OS:           Linux 6.2.0-1019-azure amd64
+
+
+
+

After Gradle is installed, you can configure the build.gradle file in your project to include the MicroProfile dependencies.

+
+
+

Whether you opt for Maven’s stability and convention or Gradle’s flexibility and performance, understanding how to configure and use your chosen build tool is important for MicroProfile development.

+
+
+
+
+

Integrated Development Environments

+
+

Integrated Development Environments (IDEs) enhance developer productivity by providing a rich set of features and extensions such as project boostraping, dependency management, intelligent code completion, configuration assistance, test runners, build, hot deployment and debugging tools. For MicroProfile development, the choice of IDE can significantly affect your development speed and efficiency. Below is a list of popular IDEs and their key features related to Java and MicroProfile development:

+
+
+

Eclipse for Enterprise Java and Web Developers

+
+

Overview: Eclipse for Enterprise Java and Web Developers is a widely used IDE for Java development, offering extensive support for Java EE, Jakarta EE, and MicroProfile, among other technologies.

+
+
+

Getting Started: The official Eclipse documentation containing instructions about creating Java projects - Creating your first Java Project

+
+
+
+

IntelliJ IDEA

+
+

Overview: IntelliJ IDEA by JetBrains supports a wide range of programming languages and frameworks, including Java, Kotlin, and frameworks like Spring, Jakarta EE, and MicroProfile.

+
+
+

Getting Started: Refer to this IntelliJ IDEA guide on Creating a Java Project Using IntelliJ IDEA 2024.1.

+
+
+
+

Apache NetBeans

+
+

Overview: NetBeans is an open-source IDE that supports Java development, including Java SE, Java EE, JavaFX, and more.

+
+
+

Getting Started: Check out this NetBeans Java Quick Start Tutorial for a tutorial on creating a Java application.

+
+
+
+

Visual Studio Code

+
+

Overview: Visual Studio Code is a lightweight, powerful source code editor that supports Java development through extensions.

+
+
+

Getting Started: To start with Java in VS Code, follow this Getting Started with Java in VS Code documentation.

+
+
+

Selecting an IDE should be based on personal preference, as the best choice varies depending on individual needs, familiarity, and the specific features that enhance your productivity. Each IDE offers unique advantages for MicroProfile development.

+
+
+
+
+

Setting up MicroProfile Runtime

+
+

MicroProfile applications need a runtime that supports MicroProfile specifications or a MicroProfile-compatible server to run your applications. Below are some popular options, each with unique features tailored to different needs:

+
+
+

Open Liberty

+
+

Open Liberty is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease. Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance.

+
+
+

Downloading Open Liberty page provides access to its latest releases and documentation to help you set up your environment.

+
+
+
+

Quarkus

+
+

Quarkus is known for its container-first approach, offering fast startup times and low memory footprint. It aims to optimize Java for Kubernetes and cloud environments

+
+
+

This Getting Started with Quarkus page will guide you through creating your first Quartus project and exploring its cloud-native capabilities.

+
+
+
+

Payara Micro

+
+

Payara Micro is a lightweight middleware platform suited for containerized Jakarta EE and MicroProfile applications.

+
+
+

The Payara Platform Community Edition enables easy packaging of applications into a single, runnable JAR file, simplifying deployment and scaling in cloud environments. This site about Payara Platform Community Edition offers downloads and documentation to get started.

+
+
+
+

WildFly

+
+

WildFly is a flexible, lightweight, managed application runtime that offers full Jakarta EE and MicroProfile support. WildFly is designed for scalability and flexibility in both traditional and cloud-native environments.

+
+
+

WildFly Downloads page offers the latest versions and documentation to get you started.

+
+
+
+

Helidon

+
+

Developed by Oracle, Helidon MP implements MicroProfile specifications. It provides a familiar programming model for Jakarta EE developers and enables efficient microservice development.

+
+
+

Helidon Documentation provides comprehensive resources to help developers get started with the framework, understand its core concepts, and develop microservices efficiently.

+
+
+
+

Apache TomEE

+
+

Apache TomEE integrates several Apache projects with Apache Tomcat to provide a Jakarta EE environment. It offers support for MicroProfile, allowing developers to build and deploy microservices using the well-known Jakarta EE technologies with additional MicroProfile capabilities.

+
+
+

TomEE Downloads and TomEE MicroProfile Documentation page provide the necessary resources to get started with TomEE for MicroProfile development.

+
+
+
+
+

MicroProfile Starter

+
+

To kickstart your MicroProfile project, use the MicroProfile Starter to generate a sample project with your chosen server and specifications. This tool provides a customizable project structure and generates necessary boilerplate code and configuration.

+
+
+
    +
  • +

    Visit the MicroProfile Starter page - the official website for generating the MicroProfile project templates.

    +
  • +
  • +

    Provide a groupId for your project, it’s an identifier for your project and should be unique to avoid conflicts with other libraries or projects.

    +
  • +
+
+
+ + + + + +
+ + +Its recommended convention is to start your groupId with the reverse domain name of your organization (for example, io.microprofile). +
+
+
+
    +
  • +

    Enter the 'artifactID', which is the name of your project (e.g., 'mp-ecomm-store').

    +
  • +
  • +

    Select the Java SE version your project will use.

    +
  • +
  • +

    Select the MicroProfile version you want to use. Ideally, you should choose the latest version for the most up-to-date features but also consider the runtime’s support.

    +
  • +
  • +

    Select the specifications you want to include in your project. These could be Config, Fault Tolerance, JWT Auth, Metrics, Health, Open API, Open Tracing, Rest Client. Choose what is relevant to your application.

    +
  • +
  • +

    Click the Download button.

    +
  • +
  • +

    Unzip the generated project and import it into your IDE.

    +
  • +
+
+
+

This completes the development environment setup. Now we are all set to begin development using MicroProfile.

+
+
+ + + + + +
+ + +At the time of writing this tutorial, the latest MicroProfile released version was 6.1. The MicroProfile Starter does not currently support this version. Hence, we will not be using MicroProfile Starter to generate the project structure. +== Creating a Java Project for MicroProfile Development +
+
+
+
+

Using Your IDE

+
+

Most modern IDEs have built-in support for creating Java and Maven projects. Depending on your chosen IDE, look for options like "New Project", or "New Maven Project" to get started. These options typically guide you through the setup process, including specifying the project’s groupId, artifactId, and dependencies.

+
+
+
+

Using Maven from Command Line (Terminal)

+
+

For developers who prefer using the command line or for those setting up projects in environments without an IDE, Maven can generate the base structure of a Java project through its archetype mechanism.

+
+
+

To create a project using Maven, open your terminal or command line and run the following command:

+
+
+
+
mvn archetype:generate -DgroupId=io.microprofile.tutorial -DartifactId=store
+-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
+
+
+
+

The above command creates a new Maven project based on the maven-archetype-quickstart archetype, setting the groupId as io.microprofile.tutorial and the artifactID as store.

+
+
+

Explanation:

+
+
+
    +
  • +

    mvn archetype:generate goal in this command initializes the project creation process and instructs Maven to generate a new project based on an archetype template.

    +
  • +
  • +

    -DgroupId=io.microprofile.tutorial Specifies the groupId for the project. The groupId is a unique identifier for your project and is usually based on the domain name of your organization in reverse. In this case, io.microprofile.tutorial represent a project related to MicroProfile tutorial.

    +
  • +
  • +

    -DartifactId=store: Sets the artifactId for the project. The artifactId is the name of the project and is used as the base name for the project’s artifacts (e.g., WAR files). Here, store is used as the project name to indicate this project is related an e-commerce store application.

    +
  • +
  • +

    -DarchetypeArtifactId=maven-archetype-quickstart: Indicates which archetype to use for generating the project. The maven-archetype-quickstart is a basic template for a Java application, providing a simple project structure that includes a sample application (App.java) and a unit test (AppTest.java).

    +
  • +
  • +

    -DinteractiveMode=false: This option disables interactive mode, meaning Maven will not prompt you for input during the project generation process. All necessary information is provided via the command-line options, allowing the command to execute without further user interaction.

    +
  • +
+
+
+

Next, using your file explorer or command line, create the following folder structure:

+
+
+
+
.
+├── pom.xml
+├── README.adoc
+└── src
+    └── main
+    │   └── java
+    │       └── io
+    │           └── microprofile
+    │               └── tutorial
+    │                  └── store
+    │                      └── product
+    │                      │    ├── entity
+    │                      │    │   └── Product.java
+    │	                   │    └── resource
+    │                      │       └── ProductResource.java
+    │                      └── ProductRestApplication.java
+    └── test
+	└── java
+            └── io
+                └── microprofile
+                    └── tutorial
+                       └── store
+			   └── product
+                               └── ProductServiceTest.java
+
+
+
+

The standard location for your Java source code is this folder:

+
+
+
+
src/main/java
+
+
+
+

And, the standard location for your test code is this folder:

+
+
+
+
src/test/java
+
+
+
+

You can delete App.java and AppTest.java files from the poject as these are not required in MicroProfile development.

+
+
+

The heart of your Maven project is pom.xml (Project Object Model) file. It defines project metadata, dependencies, build configurations and plugins.

+
+
+

The content for the pom.xml file should look as below, ensure MicroProfile depencency is added:

+
+
+
+
<?xml version="1.0" encoding="UTF-8"?>
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>io.microprofile.tutorial</groupId>
+  <artifactId>mp-ecomm</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>war</packaging>
+
+  <!-- Setting the source and target of the Java Compiler !>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+    <!-- Setting the source and target of the Java Compiler -->
+    <maven.compiler.source>17</maven.compiler.source>
+    <maven.compiler.target>17</maven.compiler.target>
+...
+...
+   <!-- Add Lombok dependency -->
+   <dependency>
+     <groupId>org.projectlombok</groupId>
+     <artifactId>lombok</artifactId>
+     <version>1.18.26</version>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- Adding Jakarta EE dependencies -->
+   <dependency>
+     <groupId>jakarta.platform</groupId>
+     <artifactId>jakarta.jakartaee-api</artifactId>
+     <version>10.0.0</version>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- Adding MicroProfie dependency -->
+   <dependency>
+     <groupId>org.eclipse.microprofile</groupId>
+     <artifactId>microprofile</artifactId>
+     <version>6.1</version>
+     <type>pom</type>
+     <scope>provided</scope>
+   </dependency>
+
+   <!-- JUnit Jupiter API for writing tests -->
+   <dependency>
+       <groupId>org.junit.jupiter</groupId>
+       <artifactId>junit-jupiter-api</artifactId>
+       <version>5.8.2</version>
+       <scope>test</scope>
+   </dependency>
+
+   <!-- JUnit Jupiter Engine for running tests -->
+   <dependency>
+       <groupId>org.junit.jupiter</groupId>
+       <artifactId>junit-jupiter-engine</artifactId>
+       <version>5.8.2</version>
+       <scope>test</scope>
+   </dependency>
+...
+
+
+
+

Below is the list of essential dependencies you need to add to your Maven pom.xml for a MicroProfile project:

+
+
+
    +
  • +

    Lombok Dependency - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code.

    +
  • +
  • +

    Jakarta EE API Dependency - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications.

    +
  • +
  • +

    MicroProfile Dependency - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project.

    +
  • +
  • +

    JUnit Jupiter API for Writing Tests - Essential for writing unit tests for your MicroProfile services.

    +
  • +
  • +

    JUnit Jupiter Engine for Running Tests - Enables the execution of JUnit tests.

    +
  • +
+
+
+

These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​.

+
+
+ + + + + +
+ + +Execute the $ mvn validate command. This checks the pom.xml file for correctness, ensuring that all necessary configuration is present and valid. +== Choosing Right Modules for Your MicroProfile Application +
+
+
+

Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements.

+
+
+

Before diving into MicroProfile modules, it’s essential to have a clear understanding of your application’s requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it’s important to understand which ones best fit your project’s needs.

+
+
+

This section aims to help you make informed decisions about which MicroProfile modules to use.

+
+
+
+

Use the Entire MicroProfile Dependency

+
+

If you’re beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently.

+
+
+

For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications.

+
+
+

Maven

+
+
+
+
<!-- MicroProfile 6.1 API -->
+<dependency>
+    <groupId>org.eclipse.microprofile</groupId>
+    <artifactId>microprofile</artifactId>
+    <version>6.1</version>
+    <type>pom</type>
+    <scope>provided</scope>
+</dependency>
+
+
+
+

Gradle

+
+
+
+
dependencies {
+ compileOnly 'org.eclipse.microprofile:microprofile:6.1'
+}
+
+
+
+
+

Use Individual MicroProfile Specification Dependencies

+
+

For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application’s footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time.

+
+
+

To prevent potential conflicts or security issues associated with unused dependencies, it’s prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities.

+
+
+

The list below is provided to help you select the appropriate modules for your MicroProfile application:

+
+
+
    +
  • +

    MicroProfile Config provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.config</groupId>
+  <artifactId>microprofile-config-api</artifactId>
+  <version>3.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Health allows you to define health endpoints easily. If you’re deploying your application in a environment where the service needs to report its health status.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.health</groupId>
+  <artifactId>microprofile-health-api</artifactId>
+  <version>4.0.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Metrics offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.metrics</groupId>
+  <artifactId>microprofile-metrics-api</artifactId>
+  <version>5.1.0</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Fault Tolerance helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it.

    +
  • +
+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.fault-tolerance</groupId>
+  <artifactId>microprofile-fault-tolerance-api</artifactId>
+  <version>4.0.2</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile JWT Authentication provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services.

    +
  • +
+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.jwt</groupId>
+  <artifactId>microprofile-jwt-auth-api</artifactId>
+  <version>2.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile OpenAPI offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.openapi</groupId>
+  <artifactId>microprofile-openapi-api</artifactId>
+  <version>3.1.1</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Rest Client simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.rest.client</groupId>
+  <artifactId>microprofile-rest-client-api</artifactId>
+  <version>3.0</version>
+</dependency>
+
+
+
+
    +
  • +

    MicroProfile Telemetry integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor.

    +
  • +
+
+
+

Maven

+
+
+
+
<project>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>io.opentelemetry</groupId>
+        <artifactId>opentelemetry-bom</artifactId>
+        <version>1.29.0</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>io.opentelemetry</groupId>
+      <artifactId>opentelemetry-api</artifactId>
+    </dependency>
+  </dependencies>
+</project>
+
+
+
+
    +
  • +

    Jakarta EE Core Profile dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint.

    +
  • +
+
+
+

Maven

+
+
+
+
<dependencies>
+    <!-- Jakarta EE 10 Core Profile -->
+    <dependency>
+        <groupId>jakarta.platform</groupId>
+        <artifactId>jakarta.jakartaee-api</artifactId>
+        <version>10.0.0</version>
+        <scope>provided</scope>
+    </dependency>
+</dependencies>
+
+
+
+

For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions.

+
+
+

When choosing MicroProfile modules, start with the minimal set that meets your application’s core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment. +== Developing a RESTful Web Service

+
+
+

Web Services are very popular nowadays because they allow for building decoupled systems +– services can communicate with each other without the knowledge of each other’s implementation details. +There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) +architecture. A Jakarta RESTful Webservice is a web service that uses the Representational State Transfer (REST) architecture. +This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that +all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web +applications.

+
+
+

To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, +the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as +resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. +With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) +requests this URL, the server would return a list of products in JSON format.

+
+
+
+

Creating an Entity class

+
+

An Entity class represents a specific object, in our case a product. It contains the product’s details id and name, +and other properties like price, and quantity. To implement an entity class first, you need to create a Product class, as below:

+
+
+
+
// Product.java
+package io.microprofile.tutorial.store.product.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Product {
+   private Long id;
+   private String name;
+   private String description;
+   private Double price;
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The Product class is a Plain Old Java Object (POJO). It has an id, name, description and price property. The id property is of type Long, The name and description properties are of type String. The price property is of type Double.

    +
  • +
  • +

    @Data annotation will generate constructors, getters, and setters for all fields. By doing this, +you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of Object type as well. +Jackson cannot work with primitive types because they cannot be null.

    +
  • +
  • +

    @AllArgsConstructor generates a constructor with one argument for each field in the class. +This is useful for instantiating objects with all their fields initialized.

    +
  • +
  • +

    @NoArgsConstructor generates a default constructor +for the class.

    +
  • +
+
+
+
+

Creating a Resource class

+
+

A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving +(CRUD) operations on the resources. Let us now create a ProductResource class with a getProducts() method to return a list of +Product objects.

+
+
+
+
// ProductResource.java
+package io.microprofile.tutorial.store.product.resource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/products")
+@ApplicationScoped
+public class ProductResource {
+
+   private List<Product> products = new ArrayList<>();
+
+   public ProductResource() {
+       // Initialize the list with some sample products
+       products.add(new Product(Long.valueOf(1L), "iPhone", "Apple iPhone 15", Double.valueOf(999.99)));
+       products.add(new Product(Long.valueOf(2L), "MacBook", "Apple MacBook Air", Double.valueOf(1299.99)));
+   }
+
+   @GET
+   @Produces(MediaType.APPLICATION_JSON)
+   public List<Product> getProducts() {
+       return products;
+   }
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The ProductResource is annotated with @ApplicationScoped. This will ensure that only one instance of this class available when the +application is running.

    +
  • +
  • +

    The ProductResource class has a getProducts() method, which returns a list of products. This method is annotated with the @GET annotation, which maps this method to the GET HTTP method.

    +
  • +
  • +

    The @Produces annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a GET request to the /api/products endpoint.

    +
  • +
  • +

    RESTful web services can produce and consume many different media types, including JSON, XML, and HTML.

    +
  • +
  • +

    Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with +@Produces(MediaType.APPLICATION_JSON) it can produce JSON.

    +
  • +
+
+
+
+

Creating an Applicaiton class

+
+

Create a class named ProductRestApplication as per the code below:

+
+
+
+
// ProductRestApplication.java
+package io.microprofile.tutorial.store.product;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+@ApplicationPath("/api")
+public class ProductRestApplication extends Application{
+
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The annotation @ApplicationPath("/api") specifies that any RESTful resources registered within this application will be accessed +under the base path /api. For example, if you have a resource class named ProductResource mapped to the path /products, it would be accessible at /api/products.

    +
  • +
+
+
+
+

Building Your Application

+
+

You may build the application using the following commands from your project’s root directory:

+
+
+
+
$ mvn compile
+
+
+
+

The above command will compile your project’s source code.

+
+
+
+
$ mvn test
+
+
+
+

The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed.

+
+
+
+
$mvn package
+
+
+
+

The above command will create a deployment package.

+
+
+
+

Deploying your microservices

+
+

This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations:

+
+
+

General Considerations:

+
+
    +
  • +

    Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project.

    +
  • +
  • +

    Packaging: Decide on a packaging format (e.g., WAR file, Docker image).

    +
  • +
  • +

    Configuration: Review and adjust any runtime configuration necessary for your service.

    +
  • +
  • +

    Deployment Tools: Leverage runtime-specific tools or commands for deployment.

    +
  • +
+
+
+
+

Deployment Options

+
+

You can then deploy this application on a MicroProfile compatible server and access the web service at +http://localhost:<port>/<contextRoot>/api/products. Replace <port> with the port number on which the web server or +application server is listening. The <contextRoot> is a placeholder for the context root of the web application. +The context root is part of the URL path that identifies the base path for the application on the web server.

+
+
+

Below are the steps for popular options. Specific steps will depend on your chosen runtime.

+
+
+

Open Liberty

+
+
+

Package your application as a WAR file using Maven or Gradle by adding the packaging tag in pom.xml.

+
+
+
+
<groupId>io.microprofile.tutorial</groupId>
+<artifactId>mp-ecomm-store</artifactId>
+<version>1.0-SNAPSHOT</version>
+<packaging>war</packaging>
+
+
+
+

Add a server configuration file at the location /main/liberty/config/server.xml with the content as below:

+
+
+
+
<server description="MicroProfile Tutorial Liberty Server">
+    <featureManager>
+        <feature>restfulWS-3.1</feature>
+        <feature>jsonb-3.0</feature>
+    </featureManager>
+
+    <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
+                  id="defaultHttpEndpoint" host="*" />
+    <webApplication location="mp-ecomm-store.war" contextRoot="${app.context.root}"/>
+</server>
+
+
+
+

Add the Open Liberty configuration in the pom.xml as below:

+
+
+
+
<properties>
+   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+   <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+   <maven.compiler.source>17</maven.compiler.source>
+   <maven.compiler.target>17</maven.compiler.target>
+   <!-- Liberty configuration -->
+   <liberty.var.default.http.port>9080</liberty.var.default.http.port>
+   <liberty.var.default.https.port>9443</liberty.var.default.https.port>
+   <liberty.var.app.context.root>mp-ecomm-store</liberty.var.app.context.root>
+ </properties>
+
+
+
+

Add the Open Liberty build plugin in the pom.xml as below:

+
+
+
+
<build>
+    <finalName>${project.artifactId}</finalName>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-war-plugin</artifactId>
+            <version>3.3.2</version>
+        </plugin>
+        <plugin>
+            <groupId>io.openliberty.tools</groupId>
+            <artifactId>liberty-maven-plugin</artifactId>
+            <version>3.8.2</version>
+            <configuration>
+                <serverName>productServer</serverName>
+            </configuration>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <version>3.0.0</version>
+            <configuration>
+                <systemPropertyVariables>
+                    <http.port>${liberty.var.default.http.port}</http.port>
+                    <war.name>${liberty.var.app.context.root}</war.name>
+                </systemPropertyVariables>
+            </configuration>
+        </plugin>
+   </plugins>
+</build>
+
+
+
+
+
+

Running Your Application

+
+

Refer to your runtime’s documentation for instructions on running your MicroProfile application. +For example, Consult the Open Liberty documentation for detailed instructions: +MicroProfile - Open Liberty Docs +Finally, use the following command from the command line or terminal to run the application on Liberty server.

+
+
+
+
$ mvn liberty:run
+
+
+
+

You can also run the following command to start the liberty server in development mode.

+
+
+
+
$ mvn liberty:dev
+
+
+
+

Assuming your server is running on http://localhost:9080/, you can access your service at: +http://localhost:9080/mp-ecomm-store/api/products.

+
+
+

To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. +Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. +The response should be:

+
+
+
+
[{"description":"Apple iPhone 15","id":1,"name":"iPhone","price":999.99},{"description":"Apple MacBook Air","id":2,"name":"MacBook","price":1299.99}]
+
+
+
+

This uses an in-memory list; In a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter.

+
+
+

Quarkus

+
+
+
    +
  • +

    Build your application as a native executable or Docker image.

    +
  • +
  • +

    Run the generated executable or deploy the Docker image to a container platform.

    +
  • +
  • +

    Refer to the Quarkus documentation for deployment guides: Creating your first application - Quarkus

    +
  • +
+
+
+

Payara Micro

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR to a Payara Micro server instance.

    +
  • +
  • +

    See the Payara Micro documentation for specific instructions: Getting Started with Payara Micro

    +
  • +
+
+
+

WildFly

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR to a WildFly server instance.

    +
  • +
  • +

    Refer to the WildFly documentation for deployment details: WildFly Developer Guide

    +
  • +
+
+
+

Helidon

+
+
+
    +
  • +

    Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging).

    +
  • +
  • +

    Build your application using Gradle.

    +
  • +
  • +

    Follow the relevant Helidon documentation for deployment steps: Helidon - Getting Started

    +
  • +
+
+
+

TomEE

+
+
+
    +
  • +

    Package your application as a WAR file.

    +
  • +
  • +

    Deploy the WAR file to the TomEE server instance.

    +
  • +
  • +

    Refer to the TomEE documentation for instructions: Serverless TomEE MicroProfile

    +
  • +
+
+
+

Additional Considerations:

+
+
    +
  • +

    Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability.

    +
  • +
  • +

    Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP. +== Testing Your Microservice

    +
  • +
+
+
+

Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. +To create tests for your microservice, start by creating a class called ProductResourceTest, which contains unit tests for the ProductResource class as below:

+
+
+
+
// ProductResourceTest.java
+package io.microprofile.tutorial.store.product.resource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+
+public class ProductResourceTest {
+  private ProductResource productResource;
+
+   @BeforeEach
+   void setUp() {
+       productResource = new ProductResource();
+   }
+
+   @AfterEach
+   void tearDown() {
+       productResource = null;
+   }
+
+   @Test
+   void testGetProducts() {
+       List<Product> products = productResource.getProducts();
+
+       assertNotNull(products);
+       assertEquals(2, products.size());
+   }
+}
+
+
+
+

Explanation:

+
+
+

Below are the assertions to test the getProducts() method of ProductService: +* The assertNotNull(products); assertion checks that products are not null. It ensures the method returns a list, even if it’s empty. +* The assertEquals(2, products.size()); assertion verifies that the list contains two products.

+
+
+
+
+

Running Unit Tests with Maven

+
+

To run the unit tests defined in ProductResourceTest, follow these steps:

+
+
+
    +
  • +

    Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations.

    +
  • +
  • +

    Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests:

    +
  • +
+
+
+
+
$ mvn test
+
+
+
+

This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results.

+
+
+
    +
  • +

    Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions:

    +
  • +
  • +

    Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly.

    +
  • +
  • +

    Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected.

    +
  • +
+
+
+

By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment.

+
+
+
+
+
+

Next Steps

+
+
+

Now that you have a basic MicroProfile service, consider exploring further:

+
+
+
    +
  • +

    Adding configuration with MicroProfile Config

    +
  • +
  • +

    Implementing health checks using MicroProfile Health

    +
  • +
  • +

    Enhancing your service with MicroProfile Fault Tolerance

    +
  • +
+
+
+

Resources

+
+ +
+
+

After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture. +== Package Structure

+
+
+

The Table below provides an overview of the package structure and their purposes within a typical Java-based +microservices architecture.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageDescription

dto

Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data.

entity

Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA.

repository

Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository).

service

Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService).

resource

REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource).

common

This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes.

client

For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services.

config

Configuration classes for MicroProfile Config.

exception

Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException).

util

Helper and utility classes.

+
+

Base Package: io.microprofile.tutorial.store

+
+
+
+
io.microprofile.tutorial.store
+├── catalog
+│    ├── resource
+│    ├── config
+│    ├── exception
+│    ├── entity
+│    ├── repository
+│    ├── service
+│    └── util
+├── cart
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── client
+│    ├── exception
+│    └── util
+├── user
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── inventory
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── order
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+├── payment
+│    ├── resource
+│    ├── entity
+│    ├── service
+│    ├── repository
+│    ├── exception
+│    └── util
+└── shipment
+     ├── resource
+     ├── entity
+     ├── service
+     ├── repository
+     ├── exception
+     └── util
+----
+[glossary]
+== Glossary
+
+
+
+
+
Java Development Kit (JDK)
+
+

A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development.

+
+
Integrated Development Environment (IDE)
+
+

A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code.

+
+
RESTful Service
+
+

A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet.

+
+
Runtime Environment
+
+

The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables.

+
+
JUnit
+
+

A unit testing framework for Java, used to write and run repeatable tests.

+
+
Containerization
+
+

A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment.

+
+
Cloud Deployment
+
+

Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities.

+
+
+
+
+
+
+
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter03/chapter03.html b/build/site/microprofile-tutorial/6.1/chapter03/chapter03.html new file mode 100644 index 00000000..5ffa7f6c --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter03/chapter03.html @@ -0,0 +1,961 @@ + + + + + +Jakarta EE 10 Core Profile :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

Jakarta EE 10 Core Profile

+
+

Introduction

+
+
+

This chapter delves into the Jakarta EE 10 Core Profile, a specification designed specifically for microservices and cloud-native apps. Jakarta EE is a comprehensive framework within the Java ecosystem for crafting enterprise-grade applications. Complementing this, MicroProfile addresses the intricacies of microservices development, such as configuration management, fault tolerance, health checks, and monitoring. The foundation of MicroProfile is built on the strong and established standards of Jakarta EE, which ensures smooth integration of these modern APIs with the enterprise Java landscape.

+
+
+

In this chapter, through practical examples, we will explore the critical features of the Jakarta EE 10 Core Profile that are most relevant to microservices development, including Contexts and Dependency Injection (CDI), Jakarta RESTful Web Services (Jakarta REST, formerly JAX-RS), JSON Binding and JSON Processing.

+
+
+

By the end of this chapter, you will gain a comprehensive understanding of Jakarta EE 10 Core Profile as a foundational platform for developing microservices with MicroProfile. You will be able to appreciate the pivotal role of Jakarta EE in the MicroProfile ecosystem and how its core functionalities develop scalable, resilient, and portable cloud-native applications.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Understanding the Jakarta EE 10 Core Profile

    +
  • +
  • +

    Key Specifications in Core Profile

    +
  • +
  • +

    Managing Component Dependencies

    +
  • +
  • +

    Handling HTTP Methods and Resources

    +
  • +
  • +

    Best Practices for Building Robust and Scalable Applications

    +
  • +
+
+
+
+
+

Understanding the Jakarta EE 10 Core Profile

+
+
+

The Jakarta EE 10 Core Profile is a streamlined subset of the full Jakarta EE platform explicitly designed for building lightweight microservices and cloud-native applications. It provides a standardized foundation for smaller runtime environments, comprising of a curated selection of Jakarta EE specifications:

+
+
+
    +
  • +

    Jakarta Annotations: Enables developers to decorate their code with metadata to influence system configuration and behavior, making the code concise, readable, and maintainable.

    +
  • +
  • +

    Jakarta Contexts and Dependency Injection Lite: Facilitates the management of lifecycle contexts of stateful components and the injection of dependencies.

    +
  • +
  • +

    Jakarta Interceptors: Offers a means to intercept business method invocations and lifecycle events, ideal for implementing cross-cutting concerns such as logging.

    +
  • +
  • +

    Jakarta JSON Processing and Jakarta JSON Binding: Simplifies the parsing, generation, and binding of JSON data for Java objects, crucial for RESTful service communication.

    +
  • +
  • +

    Jakarta REST: Provides a framework for creating web services according to the REST architectural pattern, enhancing web API development.

    +
  • +
+
+
+
+
+

Key Specifications in Jakarta EE 10 Core Profile

+
+
+

Let’s delve deeper into some of the specifications included in the Jakarta Core Profile to understand their importance and functionality:

+
+
+

Jakarta Annotations

+
+

This specification simplifies the code by reducing the need for external configuration files and making the intentions behind code clear. Annotations are extensively used across various Jakarta EE specifications.

+
+
+

Key Features

+
+
    +
  • +

    Simplification of Configuration: Annotations reduce the need for XML configuration files, making the setup more straightforward and less error-prone.

    +
  • +
  • +

    Enhanced Readability and Maintenance: Code decorated with annotations is easier to read and maintain, as the configuration is co-located with the code it configures.

    +
  • +
  • +

    Wide Adoption: Used across the Jakarta EE platform for a variety of purposes, including dependency injection, defining REST endpoints, and configuring beans.

    +
  • +
+
+
+
+
+

Jakarta Contexts and Dependency Injection (CDI) - CDI Lite

+
+

CDI is the specification that unifies the Jakarta EE platform by providing a consistent way to manage the lifecycle of stateful components and their interactions. The CDI Lite section of the specification is tailored for environments where full CDI support may be too heavyweight, such as microservices and serverless deployments.

+
+
+

Key Features of Contexts and Dependency Injection (CDI) - CDI Lite

+
+
    +
  • +

    Type-safe Dependency Injection: Enables the injection of beans in a type-safe manner, reducing runtime errors and improving developer productivity.

    +
  • +
  • +

    Contextual Lifecycle Management: Manages the lifecycle of beans according to well-defined contexts, simplifying state management across different scopes.

    +
  • +
  • +

    Interceptors: Supports the use of interceptors for adding behavior to beans or for altering their behavior in a non-invasive manner.

    +
  • +
+
+
+ + + + + +
+ + +The CDI Lite Tutorial is an invaluable resource, if you are looking to gain a solid foundation in CDI Lite and its role within the Jakarta EE ecosystem, especially in the context of building lightweight microservices and cloud-native applications. It will take you through the basics, advanced features, and the practical application of CDI Lite, equipping you with the knowledge to make the most out of this powerful specification. +
+
+
+
+
+

Jakarta Interceptors

+
+

Jakarta Interceptors allow developers to define methods that intercept business method invocations and lifecycle events on Jakarta EE components. This is particularly useful for implementing cross-cutting concerns such as logging, transactions, security, and more, without cluttering business logic.

+
+
+

Key Features of Jakarta Interceptors

+
+
    +
  • +

    Separation of Concerns: Helps in separating cross-cutting concerns (like logging, transaction management, and security) from business logic.

    +
  • +
  • +

    Reusability: Interceptors can be defined once and applied to multiple beans, promoting code reuse.

    +
  • +
  • +

    Configurability: Interceptors can be enabled, disabled, or reordered through configuration, offering flexibility in their application.

    +
  • +
+
+
+ + + + + +
+ + +For an in-depth understanding of Jakarta Interceptors, We highly recommend you to read the Jakarta Interceptors Tutorial. This tutorial covers everything from basic concepts to advanced usage scenarios, providing a solid foundation for effectively utilizing interceptors in your projects. +
+
+
+
+
+

Jakarta JSON Processing

+
+

Jakarta JSON Processing (JSON-P) is a specification in the Jakarta EE platform that provides a portable API to parse, generate, transform, and query JSON data in a Java application. It is part of the larger ecosystem of Jakarta EE specifications designed to facilitate the development of enterprise applications with support for modern data formats and protocols, including JSON, which is widely used in web services and RESTful APIs.

+
+
+

Key Features of Jakarta JSON Processing

+
+
    +
  • +

    Parsing and Generation: JSON-P allows for both the parsing of JSON data into a Java representation and the generation of JSON data from Java objects. This can be done using either a streaming API for efficiency with large data sets or a more intuitive object model API for ease of use.

    +
  • +
  • +

    Object Model API: This API provides a way to build or manipulate JSON data using a DOM-like tree structure. It enables developers to create, access, and modify JSON data in a flexible manner.

    +
  • +
  • +

    Streaming API: The streaming API (JsonParser and JsonGenerator) offers a lower-level, event-based approach to parsing and generating JSON. It is highly efficient, making it suitable for processing large volumes of JSON data with minimal memory overhead.

    +
  • +
  • +

    Data Binding: While JSON-P itself does not directly support data binding (converting between JSON and Java POJOs), it lays the groundwork for such functionality, which is further extended by Jakarta JSON Binding (JSON-B).

    +
  • +
+
+
+ + + + + +
+ + +For an in-depth exploration of Jakarta JSON Processing, including understanding JSON’s syntax, its applications in web services, and the programming models for manipulating JSON data, readers are encouraged to visit the Jakarta EE tutorial. This tutorial offers comprehensive guidance on both the object and streaming models for JSON data handling, suitable for beginners and advanced users alike. Learn more at the Jakarta EE +Documentation on JSON Processing. +
+
+
+
+
+

Jakarta JSON Binding

+
+

Jakarta JSON Binding (JSON-B) is a specification within the Jakarta EE platform that provides a high-level API for converting (binding) Java objects to and from JSON documents. It sits on top of Jakarta JSON Processing (JSON-P) and offers a more convenient way to work with JSON data than manually parsing and generating JSON using JSON-P’s lower-level APIs. JSON-B is designed to simplify the task of serializing Java objects into JSON and deserializing JSON into Java objects, making it an essential tool for developing modern Java enterprise applications that interact with web services, RESTful APIs, and microservices.

+
+
+

Key Features of Jakarta JSON Binding

+
+
    +
  • +

    Automatic Binding: JSON-B can automatically bind Java objects to JSON and vice versa without requiring manual parsing, significantly simplifying code and reducing boilerplate.

    +
  • +
  • +

    Customization: It provides annotations that allow developers to customize the serialization and deserialization process, such as changing property names in JSON, including or excluding specific fields, and handling custom data types.

    +
  • +
  • +

    Support for Java Generics: JSON-B can handle complex objects, including those that use Java Generics, ensuring type safety during the binding process. +Integration with JSON-P: JSON-B is built on top of JSON-P and can seamlessly integrate with it, allowing developers to mix high-level object binding with low-level JSON processing as needed.

    +
  • +
+
+
+ + + + + +
+ + +If you are interested in diving deeper into the specifics of JSON Binding, We highly recommend you to visit the Jakarta EE tutorial. It provides detailed insights into how JSON Binding works, including the processes for converting Java objects to JSON and vice versa. This knowledge is crucial for effectively managing JSON data in Java-based enterprise applications. Learn more at the Jakarta EE Documentation on JSON Binding. +
+
+
+
+
+

Jakarta RESTful Web Services

+
+

Jakarta RESTful Web Services(Jakarta REST) is a specification for creating web services according to the Representational State Transfer (REST) architectural pattern. It provides annotations to define resources and operations, making it straightforward to develop APIs for web applications.

+
+
+

Key Features of Jakarta RESTful Web Services

+
+
    +
  • +

    Annotation-driven Development: Simplifies the creation of web services by using annotations to define resources, HTTP methods, and response types.

    +
  • +
  • +

    Flexible Data Format Support: While JSON is commonly used, JAX-RS supports a variety of data formats, providing flexibility in API design.

    +
  • +
  • +

    Client API: Includes a client API for creating HTTP requests to RESTful services, facilitating communication between microservices.

    +
  • +
+
+
+

The Jakarta EE 10 Core Profile’s focus on these specifications underscores its aim to provide a lightweight, yet comprehensive platform for developing modern Java applications suited for microservices architectures and cloud-native environments.

+
+
+ + + + + +
+ + +For those looking to master developing RESTful Web Services, we strongly encourage you to explore Jakarta RESTful Web Services Tutorial. This comprehensive tutorial offers a deep dive into the Jakarta RESTful Web Services specification, demonstrating how to create, deploy, and manage RESTful services efficiently. +
+
+
+
+
+
+
+

Managing Component Dependencies

+
+
+

Jakarta Annotations and CDI plays a central role in integrating different Jakarta EE specifications, such as Jakarta Persistence API (formerly JPA) for database operations and Jakarta RESTful Web Services (formerly JAX-RS) for web services. Let’s now enhance the product microservices we developed previously.

+
+
+

Jakarta Annotations is used for defining RESTful services and injecting dependencies. For instance, in our product microservices, we can update the Product and ProductRepository class to include annotations that facilitate entity management and dependency injection:

+
+
+

Entity class

+
+
+
package io.microprofile.tutorial.store.product.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.GeneratedValue;
+import jakarta.validation.constraints.NotNull;
+
+@Entity
+@Table(name = "Product")
+@NamedQuery(name = "Product.findAllProducts", query = "SELECT p FROM Product p")
+@NamedQuery(name = "Product.findProductById", query = "SELECT p FROM Product p WHERE p.id = :id")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Product {
+
+    @Id
+    @GeneratedValue
+    private Long id;
+
+    @NotNull
+    private String name;
+
+    @NotNull
+    private String description;
+
+    @NotNull
+    private Double price;
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    @Entity and @Table(name = "Product"): These annotations declare the class as a Jakarta Persistence entity and map it to a database table named "Product".

    +
  • +
  • +

    @Id and @GeneratedValue: These annotations denote the id field as the primary key of the entity and indicate that its value should be generated automatically.

    +
  • +
  • +

    @NotNull: This annotation from Jakarta Bean Validation ensures that the name, description, and price fields cannot be null, enforcing data integrity at the application level.

    +
  • +
  • +

    @NamedQuery: These annotations define Jakarta Persistence API named queries for common operations, such as retrieving all products or finding a product by its ids. These can be used throughout the application to interact with the database in a consistent manner.

    +
  • +
  • +

    @Data, @AllArgsConstructor, and @NoArgsConstructor: These annotations from Project Lombok automatically generate boilerplate code such as getters, setters, a no-arguments constructor, and an all-arguments constructor. This keeps the entity class concise and focused on its fields and annotations related to Jakarta Persistence.

    +
  • +
+
+
+
+

Repository class

+
+

The ProductRepository class serves as a bridge between the application’s business logic layer and the database, performing CRUD (Create, Read, Update, Delete) operations on Product entities. It exemplifies the separation of concerns, a fundamental principle in enterprise Java applications, by cleanly segregating the data access logic from the business logic.

+
+
+
+
package io.microprofile.tutorial.store.product.repository;
+
+import java.util.List;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+
+@RequestScoped
+public class ProductRepository {
+
+   @PersistenceContext(unitName = "product-unit")
+   private EntityManager em;
+
+   private List<Product> products = new ArrayList<>();
+
+   public ProductRepository() {
+        // Initialize the list with some sample products
+        products.add(new Product(1L, "iPhone", "Apple iPhone 15", 999.99));
+        products.add(new Product(2L, "MacBook", "Apple MacBook Air", 1299.0));
+   }
+
+   public void createProduct(Product product) {
+       em.persist(product);
+   }
+
+   public Product updateProduct(Product product) {
+       return em.merge(product);
+   }
+
+   public void deleteProduct(Product product) {
+       em.remove(product);
+   }
+
+   public List<Product> findAllProducts() {
+       return em.createNamedQuery("Product.findAllProducts",
+       Product.class).getResultList();
+   }
+
+   public Product findProductById(Long id) {
+       return em.find(Product.class, id);
+   }
+
+   public List<Product> findProduct(String name, String description, Double price) {
+       return em.createNamedQuery("Event.findProduct", Product.class)
+           .setParameter("name", name)
+           .setParameter("description", description)
+           .setParameter("price", price).getResultList();
+   }
+
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    ProductRepository: This class utilizes Jakarta Persistence API (JPA) for database operations, encapsulating the CRUD (Create, Read, Update, Delete) operations along with methods to find products by various criteria.

    +
  • +
  • +

    @RequestScoped: This CDI annotation for ProductRepository class indicates that an instance of this class is created for each HTTP request to ensure that database operations are handled within the context of a single request.

    +
  • +
  • +

    @PersistenceContext: This annotation injects an entity manager instance, em, specifying the persistence unit product-unit. The entity manager is +the primary JPA interface for database interactions.

    +
  • +
  • +

    The methods createProduct(), updateProduct(), deleteProduct(), findAllProducts(), and findProductById() methods define CRUD operations that might be performed by the repository. These methods utilize the EntityManager instance to persist, merge, remove, and query for product entities.

    +
  • +
  • +

    The EntityManager is responsible for managing the persistence context and performing CRUD operations on the entities.

    +
  • +
+
+
+

The ProductRepository serves as a foundational example for developers to understand how to construct a data access layer in a MicroProfile application, emphasizing the significance of CDI in managing component lifecycles and dependencies, as well as showcasing the application of Jakarta Persistence for Object Relational Mapping(ORM) based data access.

+
+
+
+

Lifecycle Management of Beans in Jakarta EE

+
+

CDI defines several built-in scopes to manage the lifecycle of beans, each corresponding to a specific context within the application. When a bean is needed, the CDI container automatically creates it within its defined scope, manages its lifecycle, and destroys it when the context ends. This process is largely transparent to the developer, simplifying development.

+
+
+ + + + + +
+ + +To learn more about using built-in scopes in CDI for the lifecycle management of beans, We highly recommend visiting the Using Scopes section of the Jakarta EE Tutorial. This resource provides valuable insights into each scope and how to use them effectively in your applications. +
+
+
+
+
+
+

Handling HTTP Methods and Resources

+
+
+

Jakarta RESTful Web Services annotations are utilized to define endpoints for the web services, facilitating the creation and management of RESTful APIs. The ProductResource class demonstrates this:

+
+
+
+
package io.microprofile.tutorial.store.product.resource;
+
+
+import java.util.List;
+
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import io.microprofile.tutorial.store.product.repository.ProductRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/products")
+@ApplicationScoped
+public class ProductResource {
+
+    private static final Logger LOGGER = Logger.getLogger(ProductResource.class.getName());
+
+    // ...
+
+    @Inject
+    private ProductRepository productRepository;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getAllProducts() {
+        LOGGER.info("Fetching all products");
+        return Response.ok(products).build();
+    }
+
+    // Additional endpoint methods
+}
+
+
+
+

The @ApplicaitonScoped is an CDI annotation that specifies that the bean is application-scoped, meaning there will be a single instance of ProductResource for the entire application, which promotes better resource utilization and performance.

+
+
+

The @Inject annotation is commonly used in CDI to inject instances into the application classes without needing to do manual lookups or new instance creations. For example, When ProductResource needed a repository to fetch products from a database, we used @Inject to incorporate that repository seamlessly.

+
+
+

@Path and @GET: Defines the URI path and HTTP method for accessing the getProducts endpoint.

+
+
+
+
+

Defining RESTful APIs

+
+
+

When creating a REST API, you typically start by defining the resources that your API will expose. A unique URI identifies each resource. You then define the operations that can be performed on each resource. These operations are typically CRUD operations: create, read, update, and delete. +Let us now create a RESTful API to manage a list of products for a store. This RESTful API allows client applications to access the product stored as resources on the server.

+
+
+

The API is implemented using Jakarta EE and REST architectural style. The API has the following methods:

+
+
+
    +
  • +

    GET /api/products: Retrieves a list of products

    +
  • +
  • +

    POST /api/products: Creates a new product, the product details are provided as JSON in the request body

    +
  • +
  • +

    PUT /api/products: Updates an existing product, the updated product details are provided as JSON in the request body

    +
  • +
  • +

    DELETE /api/products/chapter03: Deletes a product, the product id is provided in the request URL path

    +
  • +
+
+
+

Multiple annotations can be used together in a single method to support multiple media types. For example, When both @Consumes(MediaType.APPLICATION_JSON) and @Produces(MediaType.APPLICATION_XML) are used together in a single method, then the method can consume JSON and produce XML.

+
+
+

Table 3-1 shows a list of some of the popular Media types along with their constant fields in jakarta.ws.rs.core.MediaType class and corresponding HTTP ContentType:

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Media TypeConstant FieldDescription

application/json

MediaType.APPLICATION_JSON

JSON format, used for representing structured data.

application/xml

MediaType.APPLICATION_XML

XML format, used for representing structured data in XML format.

text/xml

MediaType.TEXT_XML

XML format, primarily used for XML data that is human-readable.

text/plain

MediaType.TEXT_PLAIN

Plain text format, used for unstructured text data.

text/html

MediaType.TEXT_HTML

HTML format, used for markup data that can be rendered by web browsers.

application/octet-stream

MediaType.APPLICATION_OCTET_STREAM

Binary data stream, used for transmitting files or streaming.

application/x-www-form-urlencoded

MediaType.APPLICATION_FORM_URLENCODED

Web form format, used for submitting form data in HTTP requests.

multipart/form-data

MediaType.MULTIPART_FORM_DATA

Multipart format, used for uploading files through web forms.

application/vnd.api+json

Custom

JSON API format, a specification for how clients should request and modify resources.

application/hal+json

Custom

Hypertext Application Language (HAL) JSON format, used for linking between resources in APIs.

+
+

Implementing REST APIs for Managing Products Data

+
+

After having successfully performed the development and testing of the GET method of ProductResource to fetch the list of product resources. Let’s now call the create, update and delete methods for our Products REST API. For this you only need to add additional methods of our ProductResource class.

+
+
+

Creating a Product

+
+
+
@POST
+@Consumes(MediaType.APPLICATION_JSON)
+@Transactional
+public Response createProduct(Product product) {
+   System.out.println("Creating product");
+   productRepository.createProduct(product);
+   return Response.status(Response.Status.CREATED)
+         .entity("New product created").build();
+}
+
+
+
+

Explanation:

+
+
+

The createProduct() method is annotated with @POST, which means it can be invoked via an HTTP POST request. The @Consumes(MediaType.APPLICATION_JSON) annotation says it will consume JSON data. This method takes a single parameter, which is of type Product`. This parameter will be populated with the data sent in the HTTP POST request. The method creates a new Product object and adds it to the list of products. Finally, the method returns a Response object with a status code of 201 (Created) and a message indicating that a new product has been created.

+
+
+
Verifying the POST request
+
+

You can use a REST client such as Postman or the cURL command line utility to test the HTTP methods (including PUT, POST, DELETE). To verify the POST request, you can use the following cURL command. This sends a JSON object representing a new product to your microservice.

+
+
+

Command:

+
+
+
+
$ curl -H 'Content-Type: application/json' -d '{ "id": "3", "name":"iPhone 14", "description":"Apple iPhone 14", "price":"799.99"}' -X POST http://localhost:9080/mp-ecomm-store/api/products
+
+
+
+

Output:

+
+
+
+
New product created
+
+
+
+

This command specifies the content type as JSON and sends a data payload representing a product with an ID of 3, the name "iPhone 14", a description of "Apple iPhone 14", and a price of 799.99. The -X POST parameter indicates that this is a POST request. Upon successful execution, your service should process this data and add the new product to the database.

+
+
+

Next you can verify the addition of the new product, by calling the GET method using cURL or browser as described previously to list all products. This request should now return an updated list of products, including the newly added product.

+
+
+
+
$ curl http://localhost:9080/mp-ecomm-store/api/products
+
+
+
+
+
+
+

Updating a Product

+
+

Updating existing product information is a common operation for RESTful services managing a catalog of items. The PUT request method is designed for these scenarios, allowing you to modify an existing product’s details. The code snippet below demonstrates updating the product:

+
+
+
+
@PUT
+@Consumes(MediaType.APPLICATION_JSON)
+@Transactional
+public Response updateProduct(Product product) {
+   // Update an existing product
+   Response response;
+   System.out.println("Updating product");
+   Product updatedProduct = productRepository.updateProduct(product);
+
+   if (updatedProduct != null) {
+       response = Response.status(Response.Status.OK)
+                .entity("Product updated").build();
+   } else {
+       response = Response.status(Response.Status.NOT_FOUND)
+               .entity("Product not found").build();
+   }
+   return response;
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The @PUT annotation defines that the method updateProduct() can be invoked via an HTTP PUT request.

    +
  • +
  • +

    As in the POST method, the @Consumes(MediaType.APPLICATION_JSON) annotation specifies the method will consume JSON data. This method takes a single parameter, which is of type Product. This parameter will be populated with the data sent in the HTTP PUT request. The method updates the product with the same id as the one sent in the request.

    +
  • +
  • +

    If a product with the same id is not found, the method returns a 404 (Not Found) error. Finally, the method returns a Response object with a status code of 204 (No Content) and a message indicating that an existing product has been updated.

    +
  • +
+
+
+

Verifying the PUT request

+
+

To test the PUT request, you can use the following cURL command.

+
+
+
+
$ curl -H 'Content-Type: application/json' -d '{ "id": "3","name":"iPhone14", "description":"Apple iPhone 14", "price":"749"}' -X PUT  http://localhost:5050/mp-ecomm-store/api/products
+
+
+
+

Next you can verify the updation of the new product, by calling the GET method using cURL or browser as described previously to list all products.

+
+
+
+
$ curl http://localhost:9080/mp-ecomm-store/api/products
+
+
+
+
+
+

Deleting a Product

+
+
+
@DELETE
+@Path("products/{id}")
+public Response deleteProduct(@PathParam("id") Long id) {
+     // Delete a product
+     Response response;
+     System.out.println("Deleting product with id: " + id);
+     Product product = productRepository.findProductById(id);
+     if (product != null) {
+         productRepository.deleteProduct(product);
+         response = Response.status(Response.Status.OK)
+                 .entity("Product deleted").build();
+     } else {
+        response = Response.status(Response.Status.NOT_FOUND)
+                .entity("Product not found").build();
+     }
+     return response;
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The @DELETE annotation defines that the method deleteProduct() can be invoked via an HTTP DELETE request.

    +
  • +
  • +

    The @Path annotation specifies the id path parameter that will be used to identify which product to delete. This method takes a single parameter of type Long and is annotated with the @PathParam annotation. This parameter will be populated with the id path parameter from the HTTP DELETE request.

    +
  • +
  • +

    The method deletes the product with the same id as the one sent in the request. If a product with the same id is not found, the method returns a 404 (Not Found) error. Finally, the method returns a Response object with a status code of 204 (No Content) and a message indicating that an existing product has been deleted.

    +
  • +
+
+
+ + + + + +
+ + +The code demonstrated in this chapter is not production quality. It was highly simplified to explain to you the fundamental principles of the REST API. In the upcoming chapters, you will be further building upon this code. By implementing many features from the latest MicroProfile and Jakarta EE standards, you would be making it more a robust microservice that is also cloud-ready. You would also learn to containerize, scale, deploy and manage this application. +
+
+
+
+
+
+

Summary

+
+
+

This chapter has laid a solid foundation on the Jakarta EE 10 Core Profile, emphasizing its crucial role in the development of microservices using MicroProfile. By delving into key specifications and through practical implementation examples, you have been equipped with the necessary knowledge to utilize the Jakarta EE 10 Core Profile’s features for creating scalable, resilient, and portable cloud-native applications.

+
+
+

Additionally, this chapter guided you through the creation of RESTful web services using Jakarta EE Restful Web Services APIs, providing an overview of REST (Representational State Transfer), it aimed to familiarize you with the basics of REST, enabling you to create and deploy a RESTful web service independently.

+
+
+

As we move forward, the next chapter will delve deeper into the REST architectural pattern, exploring standard conventions, design considerations, and best practices. It will cover many advanced concepts essential for building RESTful web services tailored for cloud-native and microservices-based applications, preparing you for more sophisticated aspects of modern application development.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter04/chapter04.html b/build/site/microprofile-tutorial/6.1/chapter04/chapter04.html new file mode 100644 index 00000000..d4c79ad9 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter04/chapter04.html @@ -0,0 +1,626 @@ + + + + + +MicroProfile OpenAPI :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile OpenAPI

+
+

Introduction

+
+
+

In the previous chapter, we saw how RESTful APIs facilitate language-agnostic access to web services from diverse environments. However, a clear and comprehensive contract is required to ensure seamless integration between clients and services. This need for a well-defined API contract has led to the adoption of the OpenAPI specification. +This chapter will explore the primary features of MicroProfile OpenAPI, demonstrate how to integrate it into your MicroProfile applications, and show you how to annotate your RESTful services to produce rich documentation that adheres to the OpenAPI specification. Furthermore, we will introduce the OpenAPI UI, a visual interface allowing developers and stakeholders to interact with and visualize the documented APIs, enhancing understanding and facilitating integration.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Introduction to MicroProfile OpenAPI

    +
  • +
  • +

    API Specification using MicroProfile Open API

    +
  • +
  • +

    Generating API Documentation

    +
  • +
  • +

    Documenting Authentication and Authorization Requirements

    +
  • +
  • +

    Exploring the APIs using Swagger UI

    +
  • +
+
+
+
+
+

OpenAPI Specification

+
+
+

The Open API Specification (OAS), formerly Swagger specification, is a technical specification that allows REST API providers to describe and publish their APIs using a format that various tools can consume. It defines a standard, language-agnostic interface to RESTful APIs, making it easy for third-party tools to generate documentation, client SDKs, and a range of tools that promote the seamless consumption of RESTful APIs.

+
+
+ + + + + +
+ + +The OpenAPI Initiative, a consortium of industry experts committed to standardizing how to describe REST APIs, maintains the OpenAPI Specification. It is a community-driven initiative, and many large organizations use it, including Google, Microsoft, and Amazon. +
+
+
+

The OpenAPI specification enables creation of a well-defined, clear and comprehensive API contract. It provides a standardized way to describe the API’s structure, expected requests and responses, and authentication mechanisms, making it easier to develop, test, and maintain RESTful APIs.

+
+
+
+
+

Introduction to MicroProfile OpenAPI

+
+
+

The MicroProfile OpenAPI specification builds upon the widely recognized OpenAPI Specification (OAS) and leverages annotations from the Jakarta Restful Web Services specification. The primary focus of MicroProfile OpenAPI is on defining REST APIs that utilize JSON within the context of HTTP.

+
+
+

The specification aims to provide a uniform way of describing APIs so that they are both human-readable and machine-readable. It facilitates the creation of APIs that are consistent, well-documented, and easily consumable by both humans and machines.

+
+
+
+
+

Capabilities of MicroProfile OpenAPI Specification

+
+
+

MicroProfile OpenAPI provides a suite of Java APIs that allows developers to define and generate API specifications that adhere to OpenAPI v3 standards. As a result, it simplifies the process of designing, documenting, and publishing RESTful APIs for developers.

+
+
+

Developers can quickly generate documentation for their microservices using MicroProfile OpenAPI. The documentation includes information on what services are provided, how to invoke them, and what data types are used. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools. Also, documentation can generate client code to access the web services.

+
+
+

The OpenAPI Specification fuels a rich ecosystem of tools that automate and support. This specification streamlines the creation of OpenAPI documentation for RESTful services using a unified approach. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools:

+
+
+
    +
  • +

    API Documentation Generation: Intuitive interactive documentation portals emerge directly from the specification.

    +
  • +
  • +

    Client SDK Creation: Client libraries in various languages can be automatically generated.

    +
  • +
  • +

    API Testing: Testing frameworks can leverage the specification to design robust tests.

    +
  • +
  • +

    API Mocking: Simplifies mocking APIs for testing and development purposes.

    +
  • +
+
+
+
+
+

Generating OpenAPI documents

+
+
+

There are multiple ways in which you can generate OpenAPI documents. The most common way is to use annotations. This only requires augmenting your Jakarta Restful Web Services annotations with OpenAPI annotations.

+
+
+

Besides annotations, a predefined OpenAPI document may be provided in either YAML or JSON format. This so-called static model will be merged with the model generated by scanning for Jakarta REST endpoints and the combined result will be made available to clients. However, the annotation-based approach is recommended as it is more maintainable and easier to understand. Finally, you can filter out the resources you do not want to document using configuration.

+
+
+
+
+

Using MicroProfile Open API in your project

+
+
+

To document Jakarta RESTful Web Services using MicroProfile OpenAPI, we need to annotate the resource classes and methods with the OpenAPI annotation.

+
+
+

To use MicroProfile OpenAPI in your project, you need to add the following maven coordinates to your project:

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.openapi</groupId>
+  <artifactId>microprofile-openapi-api</artifactId>
+  <version>3.1.1</version>
+</dependency>
+
+
+
+

Below is an illustrative example of how you might annotate a method in the ProductResource class to achieve this documentation using MicroProfile OpenAPI annotations:

+
+
+
+
import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@ApplicationScoped
+@Path("/products")
+@Tag(name = "Product Resource", description = "CRUD operations for products")
+public class ProductResource {
+
+    //...
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "List all products", description = "Retrieves a list of all available products")
+    @APIResponses(value = {
+        @APIResponse(
+            responseCode = "200",
+            description = "Successful, list of products found",
+            content = @Content(mediaType = "application/json",
+                    schema = @Schema(implementation = Product.class))
+        ),
+        @APIResponse(
+            responseCode = "400",
+            description = "Unsuccessful, no products found",
+            content = @Content(mediaType = "application/json")
+        )
+    })
+    public List<Product> getAllProducts() {
+        // Method implementation
+    }
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    @Operation: Provides a summary and description for the getProducts() method.

    +
  • +
  • +

    @APIResponse: Describes the possible responses from the getProducts() operation. In this case, a successful response (HTTP 200) is described, indicating that the method returns an array of Product entities.

    +
  • +
  • +

    @Schema: Specifies the schema of the response content. Here, it is used to indicate that the method returns an array of Product objects.

    +
  • +
+
+
+

These annotations enrich the ProductResource class with metadata necessary for generating comprehensive and descriptive OpenAPI documentation automatically.

+
+
+

We have also annotated the getProducts() method with the @APIResponse annotation to document the successful response from the operation. The responseCode field is used to specify the status code of the response, and the description field is used to provide a brief description of the response. There are two possible responses – a successful response containing a list of produdts with a 200 status code, and an unsuccessful response with a 400 status code, if no products are found. The content field is used to specify the schema of the response content. In this example, the response content is a list of `Product`s.

+
+
+

Finally, we need to add the following property to the src/main/resources/META-INF/microprofile-config.properties file:

+
+
+
+
mp.openapi.scan=true
+
+
+
+

This property tells MicroProfile OpenAPI to scan our classes for annotations and generate API documentation for them.

+
+
+

Now that we have configured MicroProfile OpenAPI, we can build and run our application.

+
+
+
+
+

How to view the generated documentation

+
+
+

To view the generated documentation, we can use the OpenAPI UI tool. The Open API UI tool is a web-based tool that can be used to view the documentation for a REST API.

+
+
+

The OpenAPI UI tool can be accessed at the following URL:

+
+
+
+
http://localhost:<port>/openapi/
+
+
+
+

Replace <port> with the actual port used by your runtime, for e.g. 9080 which is the default port at Open Liberty server.

+
+
+

The /openapi endpoint is used to get information about the OpenAPI specification generated from the comments in the source code annotations. It returns information in YAML format.

+
+
+

When we access the http://localhost:5050/openapi URL, we should see the API documentation that was generated by MicroProfile OpenAPI:

+
+
+
+
openapi: 3.0.3
+info:
+  title: Generated API
+  version: "1.0"
+servers:
+- url: http://localhost:9080/catalog
+paths:
+  /api/products:
+    get:
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Product'
+    put:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Product'
+      responses:
+        "200":
+          description: OK
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Product'
+      responses:
+        "200":
+          description: OK
+  /api/products/products/{id}:
+    delete:
+      parameters:
+      - name: id
+        in: path
+        required: true
+        schema:
+          format: int64
+          type: integer
+      responses:
+        "200":
+          description: OK
+  /api/products/{id}:
+    get:
+      parameters:
+      - name: id
+        in: path
+        required: true
+        schema:
+          format: int64
+          type: integer
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Product'
+components:
+  schemas:
+    Product:
+      required:
+      - name
+      - description
+      - price
+      type: object
+      properties:
+        id:
+          format: int64
+          type: integer
+        name:
+          type: string
+        description:
+          type: string
+        price:
+          format: double
+          type: number
+
+
+
+

As we can see, MicroProfile OpenAPI has generated API documentation for our resource class. We can use this documentation to learn about the API and how to use it.

+
+
+

MicroProfile OpenAPI allows developers to produce these specifications directly from their codebase, leveraging annotations and/or providing OpenAPI documents statically. This direct generation ensures that the API documentation is always up to date with the code.

+
+
+
+
+

Exploring the APIs using Swagger UI

+
+
+

To open Swagger UI for the API documentation generated using MicroProfile OpenAPI, you will need to deploy your application to a server that supports MicroProfile, such as Open Liberty, WildFly, Quarkus, or Payara Micro. These servers automatically generate the OpenAPI documentation for your RESTful services based on the annotations in your code.

+
+
+

Next, visit the following URL to launch the Swagger UI:

+
+
+
+
http://localhost:9080/openapi/ui
+
+
+
+

Swagger UI is then used to render this documentation in a user-friendly web interface. Below is the screenshot of swagger UI for the Product REST Resource.

+
+
+
+MicroProfile OpenAPI +
+
Swagger UI 1. Swagger UI
+
+
+
+
+

Annotations

+
+
+

The MicroProfile OpenAPI annotations can be used to document any Jakarta Restful Web Services resource. The annotations can also be used in conjunction with other Jakarta Restful Webservices annotations, such as @Path and @Produces. The most common annotations that are used to document RESTful web services are list in Table 4-1.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AnnotationsDetails

@OpenAPIDefinition

Provides metadata about the entire API. It can include information such as the title, description, version, terms of service, and contact information.

@Info

Used inside @OpenAPIDefinition to provide API metadata like title, version, description.

@Contact

Specifies contact information for the API, used within @Info.

@License

Defines the license information for the API, also used within @Info.

@Operation

Describes a single API operation on a resource.

@APIResponse

It is used to document a response from an operation.

@APIResponses

A container for multiple @APIResponse annotations, allowing documentation of different responses for a single API operation.

@RequestBody

Describes the request body of an HTTP request, specifying the content of the body and whether it is required.

@Schema

Provides schema details for a response or request body, specifying the data type, format, and constraints.

@Parameter

Provides information on parameters to the operation, including query parameters, header parameters, and path parameters.

@Tag

Adds metadata to a single tag that is used by the Operation. It helps in categorizing operations by resources or any other qualifier.

@Content

Specifies the media type and schema of the operation’s request or response body.

@Components

Allows the definition of reusable components such as schemas, responses, parameters, and more, which can be referenced by other annotations.

@SecurityRequirement

Specifies a security requirement for an operation, referencing security schemes defined in the @Components.

@ExternalDocumentation

Provides additional external documentation for an API or operation.

@Callback

Specifies a callback URL for an asynchronous operation.

@Callbacks

Specifies multiple @Callback annotations.

@Server

Describes a server that hosts the API, specifying URL and description, which can be global or specific to operations or paths

+
+

All of these annotations are defined in the org.eclipse.microprofile.openapi.annotations package.

+
+
+
+
+

Summary

+
+
+

By integrating the MicroProfile OpenAPI, developers can generate detailed, OpenAPI-compliant documentation automatically, fostering better understanding and interaction among services. By annotating ProductResource class, we generated API documentation as per Open API specification. This will ensure the services are readily discoverable, understandable, and usable, thereby accelerating development cycles and fostering a more robust and collaborative developer ecosystem.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter05/chapter05.html b/build/site/microprofile-tutorial/6.1/chapter05/chapter05.html new file mode 100644 index 00000000..a9a69c63 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter05/chapter05.html @@ -0,0 +1,1007 @@ + + + + + +MicroProfile Configuration :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Configuration

+
+
+
+

This chapter focuses on MicroProfile Configuration, a key feature that allows developers to externalize configuration properties from their code. You can adapt configuration parameters to different environments (development, testing, production) without altering the core code. It provides flexibility and adaptability for microservices in different environments.

+
+
+
+
+

Topics to be covered

+
+
+
    +
  • +

    Understanding MicroProfile Configuration

    +
  • +
  • +

    Different environments required for Microservices development

    +
  • +
  • +

    Working with Various Configuration Sources

    +
  • +
  • +

    Key capabilities of MicroProfile Configuration

    +
  • +
  • +

    Implementing Configuration Properties

    +
  • +
  • +

    Creating a Custom Configuration Source

    +
  • +
  • +

    Dynamic Updates and Handling Configuration Change Events

    +
  • +
  • +

    Managing Configuration for Different Environments

    +
  • +
  • +

    Securing Configuration and Best Practices

    +
  • +
+
+
+
+
+

Understanding MicroProfile Configuration

+
+
+

MicroProfile Configuration is a specification that allows developers to inject configuration values into applications. The MicroProfile Configuration APIs will enable developers to externalize configuration and access it from within your application. By separating configuration data (like database URLs, API credentials, feature flags) from the codebase, you make it easier to modify these settings without recompiling and redeploying the application.

+
+
+

For instance, with MicroProfile Config, you can configure connection settings for a database enhancing flexibility and adaptability across different environments in our MicroProfile e-commerce application. You can update configurations seamlessly, sometimes even while the application is running (for dynamic config sources), minimizing downtime and streamlining deployment processes. This is essential for microservices that may run in diverse setups.

+
+
+
+
+

Different environments required for Microservices development

+
+
+

When developing microservices, it’s essential to establish various +environments to accommodate different stages of the development +lifecycle. Each environment serves distinct purposes, ensuring the +application is thoroughly tested, secure, and efficient before its +deployment to production. Below are the critical environments typically +set up for developing microservices:

+
+
+
    +
  • +

    Development Environment—Developers write new code, implement features, +and perform basic unit testing in this environment, which is where the +initial development occurs. This environment is usually configured to +use local or development databases with dummy data for testing. The +logging level used in this environment is generally verbose for +debugging purposes.

    +
  • +
  • +

    Testing or QA Environment—This environment is dedicated to rigorous +testing, including automated tests, integration tests, and manual +testing by QA engineers to identify bugs or issues. Configuration here +mirrors production settings as closely as possible and connects to a +testing database. For error tracing, detailed logging may be enabled in +this environment.

    +
  • +
  • +

    Staging Environment—This is a production-like environment for final +testing of the changes before deployment to production. It ensures that +your microservices perform as expected under production conditions. This +environment is configured with settings identical to the production +environment. It typically uses a copy of production data that is +sanitized of sensitive data.

    +
  • +
  • +

    Production Environment—This is the live environment where the +microservice is fully deployed and accessible to end-users. It’s +optimized for security, performance, and reliability and configured to +access actual user data with all security features fully enabled. +Performance monitoring tools are also set up here to ensure smooth +operations.

    +
  • +
+
+
+

Using the above set of environments, development teams can streamline +the development process, enhance quality, and ensure the microservices +are robust and ready for production use. Your development team may also +require additional environments for specific needs like automation, +penetration testing, and stress testing, depending on the unique needs +of the microservices.

+
+
+
+
+

Working with Various Configuration Sources

+
+
+

MicroProfile Config allows applications to retrieve configuration +properties from a variety of sources. By default, MicroProfile Config +includes various built-in configuration sources, but you can also define +custom sources. Below we discuss how to work with these various +configuration sources.

+
+
+

Built-in Configuration Sources

+
+

MicroProfile Config defines default configuration sources that are +automatically enabled:

+
+
+

System Properties: Configuration values defined as system properties +can be accessed by MicroProfile Config. These properties can be set at +runtime using the -D flag when starting the JVM.

+
+
+

Environment Variables: Environment variables available in the system +can be used as configuration sources. They are useful for setting +configuration properties external to the application, especially in +containerized environments.

+
+
+

MicroProfile Config Properties File: A properties file named +microprofile-config.properties can be placed in the META-INF directory +of your application. This file is particularly useful for setting +default configuration values that ship with the application.

+
+
+
+
+
+

Types of Configuration Sources

+
+
+

A static configuration source is the one where the data does not change once the application has started. Examples include the microprofile-config.properties file and most custom implementations that read from a database or a service at startup.

+
+
+

On the other hand, a dynamic configuration source is one that can change its data at runtime. System properties and some custom implementations that periodically check for changes in a remote configuration service are examples of dynamic sources.

+
+
+

MicroProfile Config allows applications to read from these dynamic sources as easily as from static ones. However, whether a configuration source supports dynamic behavior depends on its implementation.

+
+
+
+
+

Key capabilities of MicroProfile Configuration

+
+
+

The MicroProfile Configuration specifications offer a set of APIs that +enable you to handle your application’s configuration efficiently. They +allow you to easily manage and customize your application’s +configurations, making it a valuable tool for developers.

+
+
+

The MicroProfile Configuration APIs provide the following capabilities +for managing the configuration settings of your application:

+
+
+
    +
  • +

    It allows reading configuration values.

    +
  • +
  • +

    It allows applications to retrieve configuration values reliably, +supporting various sources, such as property files, system properties, +environment variables, and more.

    +
  • +
+
+
+

The MicroProfile Configuration API provides several classes, allowing +easy integration of configuration values. Below is the list of key +classes and interfaces included in the MicroProfile Configuration API:

+
+
+
    +
  • +

    Config - the class that is the main entry point to the configuration API +and provides access to configuration data. The Config class provides +static methods that can be used to access configuration properties.

    +
  • +
  • +

    ConfigProvider - a utility class for getting the Config instance. It +allows retrieving the static instance of the Config object.

    +
  • +
  • +

    ConfigBuilder - An interface used to create a Config instance manually. +It can add default sources, converters, and configuration sources.

    +
  • +
  • +

    ConfigSource - This class represents a source of configuration values. +It reads configuration data from a specific source, such as system +properties, environment variables, files, or data stores.

    +
  • +
  • +

    Property - It represents a key/value pair in the configuration data.

    +
  • +
  • +

    Converter<T> - This interface implements custom converters that convert +configuration values from String to any desired type.

    +
  • +
+
+
+

These classes and interfaces provide a robust configuration mechanism +that is easy to use and extend. Developers can leverage these APIs to +externalize configuration from their applications, making them more +flexible and more accessible to run in different environments.

+
+
+
+
+

Implementing Configuration Properties

+
+
+

The Config API allows you to define configuration properties in many ways, including property files, environment variables, and system properties. To use the Config API, we’ll need to include the following dependency in our pom.xml file:

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.config</groupId>
+  <artifactId>microprofile-config-api</artifactId>
+  <version>3.1</version>
+</dependency>
+
+
+
+

For Gradle, modify your build.gradle file with the following dependency:

+
+
+
+
implementation 'org.eclipse.microprofile.config:microprofile-config-api:3.1'
+
+
+
+

Let’s now modify the getProducts() method to return a jakarta.ws.rs.core.Response object instead of a list of Product entities directly, we can utilize the Response class to build our response. This approach allows for a more standardized and flexible API response handling, including the ability to set HTTP status codes and headers.

+
+
+

Lets create a configuration file with the name microprofile-config.properties and the content as below:

+
+
+
+
# microprofile-config.properties
+product.maintenanceMode=false
+
+
+
+

This configuration file should be placed in the src/main/resources/META-INF/ directory of your application.

+
+
+

Reading Configuration Properties

+
+

Next inject this configuration value to a private variable in the ProductResource and consume this within all the operations of this service.

+
+
+

MicroProfile Config will automatically detect and use the properties defined in this file, allowing you to externalize configuration and easily adjust the behavior of your application based on the environment in which it is deployed.

+
+
+

Below is the updated ProductResource class and getProducts() method:

+
+
+
+
package io.microprofile.tutorial.store.product.resource;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import io.microprofile.tutorial.store.product.repository.ProductRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+import java.util.List;
+
+@Path("/products")
+@ApplicationScoped
+public class ProductResource {
+
+   @Inject
+   @ConfigProperty(name="product.maintenanceMode", defaultValue="false")
+   private boolean maintenanceMode;
+
+   @Inject
+   private ProductRepository productRepository;
+
+   @GET
+   @Produces(MediaType.APPLICATION_JSON)
+   @Transactional
+
+   // OpenAPI code
+   // …
+
+   public Response getProducts() {
+
+       List<Product> products = productRepository.findAllProducts();
+
+       // If in maintenance mode, return Service Unavailable status
+       if (maintenanceMode) {
+          return Response
+                  .status(Response.Status.SERVICE_UNAVAILABLE)
+                  .entity("The product catalog service is currently in maintenance mode. Please try again later.")
+                  .build();
+
+       // If products found, return products and OK status
+       } else if (products != null && !products.isEmpty()) {
+           return Response
+                   .status(Response.Status.OK)
+                   .entity(products).build();
+
+      // If products not found, return Not Found status and message
+      } else {
+          return Response
+                  .status(Response.Status.NOT_FOUND)
+                  .entity("No products found")
+                  .build();
+      }
+   }
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    @Inject: This CDI annotation enables dependency injection. It tells the container to inject an instance of a specified bean at runtime. As we have learnt previously, dependency injection enables loose coupling between classes and their dependencies, making the code more modular, easier to test, and maintain.

    +
  • +
  • +

    @ConfigProperty(name="product.maintenanceMode", defaultValue="false"): This MicroProfile Config annotation used along with @Inject to inject configuration property values into beans. It allows developers to externalize configuration from the application code, making applications more flexible and environment-agnostic. The name parameter specifies the key of the configuration property to be injected. In this case, product.maintenanceMode is the key for a property that controls whether this service is in maintenance mode. The defaultValue provides a default value to be used if the specified configuration property is not found in any of the configured sources. Here, the default value is false, indicating that, by default, the service is not in maintenance mode unless explicitly configured otherwise.

    +
  • +
  • +

    private boolean maintenanceMode: This field is set to the value of the product.maintenanceMode configuration property. Due to the defaultValue ="false", if the configuration is not specified elsewhere, maintenanceMode will be false, meaning the service operates normally.

    +
  • +
  • +

    private ProductRepository productRepository: This field is injected with an instance of ProductRepository. This class abstracts the data access operations for products. This injection decouples the class from the specific implementation of the repository, making the code more modular and easier to adapt or replace parts of it in the future.

    +
  • +
  • +

    The getProducts() method retrieves all products from the repository by calling productRepository.findAllProducts(), which queries the database to retrieve a list of all available products. +Before proceeding to return the list of products, the method checks the maintenanceMode flag. If maintenanceMode is true, the service is currently undergoing maintenance, and thus, it is not appropriate to perform regular operations. The method constructs and returns a Response with a 503 Service Unavailable HTTP status code, along with a message indicating that the product catalog service is in maintenance mode.

    +
  • +
  • +

    If the service is not in maintenance mode, then the method checks if the list of retrieved products is not null and not empty.

    +
  • +
  • +

    If products are found, it constructs a Response with a status of 200 OK and includes the list of products as the response entity. This indicates a successful operation where product data is found and returned.

    +
  • +
  • +

    If the products list is`null` or empty, indicating no products were found, the method constructs and returns a Response with a 404 Not Found status code and a message stating that no products were found.

    +
  • +
+
+
+

When we deploy the application and invoke the /api/products endpoint, we should see the list of products as below:

+
+
+
+
[{"description":"Apple iPhone 15 Pro","id":1,"name":"iPhone 15 Pro","price":999.0}]
+
+
+
+
+

Specifying Default Values for a ConfigProperty

+
+

For non-critical properties, providing a default value using the defaultValue attribute of the @ConfigProperty annotation ensures that your application has a fallback option. We can specify a default value to be used if the property does not exist as below:

+
+
+
+
public class ProductResource {
+
+   @Inject
+   @ConfigProperty(name="product.maintenanceMode", defaultValue="false")
+   private boolean maintenanceMode;
+   …
+
+
+
+

In the example above , the false default value will be used if the product.maintenanceMode property does not exist.

+
+
+
+

Type Conversion in ConfigProperty

+
+

ConfigProperty also supports type conversion, so we can inject our configuration data into fields of any type:

+
+
+
+
   @Inject
+   @ConfigProperty(name="product.maintenanceMode", defaultValue="false")
+   private boolean maintenanceMode;
+
+
+
+

In this example, the product.maintenanceMode property will be converted to an Boolean before it is injected into the maintenanceMode field.

+
+
+
+

Converting Configuration data to a POJO

+
+

We can also use the Config API to convert our configuration data to a POJO:

+
+
+
+
import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+
+public class MyApplication {
+    @Inject
+    private MaintenanceMessage message;
+}
+
+
+public class MaintenanceMessage {
+    @ConfigProperty(name="product.maintenanceMessage")
+    private String message;
+}
+
+
+
+

In this example, we’re injecting a property named "product.maintenanceMessage" into the message field of our MaintenanceMessage class.

+
+
+
+

Creating a Custom ConfigSource

+
+

As we saw, the Config API makes it easy to inject configuration properties into an application. The Config API defines a contract for config implementations. A ConfigSource is used to read configuration data from a particular source. For example, we could create a ConfigSource that reads configuration data from a file.

+
+
+

ConfigSource interface has the following methods:

+
+
+
    +
  • +

    String getName() : Returns the name of the ConfigSource.

    +
  • +
  • +

    int getOrdinal() : Returns the ordinal of the ConfigSource. Ordinals are used to determine the precedence of ConfigSources. A higher ordinal means a higher precedence.

    +
  • +
  • +

    Map<String, String> getProperties() : Returns a map of the properties in this ConfigSource. The keys in the map are the property names, and the values are the property values.

    +
  • +
  • +

    getValue(String propertyName) : Returns the value of the given property. If the property is not found, this method returns null.

    +
  • +
  • +

    Set getPropertyNames() : Returns a Set of the property names in this ConfigSource.

    +
  • +
+
+
+

Let’s implement a feature in our MicroProfile e-Commerce application to integrate payment gateway configuration dynamically by creating a PaymentServiceConfigSource (a custom ConfigSource) which could fetch API keys and endpoints. This would ensure that payment service configurations are up-to-date and can be changed without redeploying the application.

+
+
+

The following is an implementation of a ConfigSource that reads configuration data from a file:

+
+
+
+
package io.microprofile.tutorial.store.payment.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+public class PaymentServiceConfigSource implements ConfigSource{
+
+   private Map<String, String> properties = new HashMap<>();
+
+   public PaymentServiceConfigSource() {
+       // Load payment service configurations dynamically
+       // This example uses hardcoded values for demonstration
+       properties.put("payment.gateway.apiKey", "secret_api_key");
+       properties.put("payment.gateway.endpoint", "https://api.paymentgateway.com");
+   }
+
+   @Override
+   public Map<String, String> getProperties() {
+       return properties;
+   }
+
+   @Override
+   public String getValue(String propertyName) {
+       return properties.get(propertyName);
+   }
+
+   @Override
+   public String getName() {
+       return "PaymentServiceConfigSource";
+   }
+
+   @Override
+   public int getOrdinal() {
+       // Ensuring high priority to override default configurations if necessary
+       return 600;
+   }
+
+   @Override
+   public Set<String> getPropertyNames() {
+       // Return the set of all property names available in this config source
+       return properties.keySet();}
+}
+
+
+
+

The above code snippet demonstrates MicroProfile Config’s flexibility in integrating with various external configuration providers. This enables applications to load configurations from sources beyond the default system properties, environment variables, and microprofile-config.properties files. This capability is crucial for modern applications that may need to pull configuration from dynamic sources like cloud services, databases, or custom APIs.

+
+
+ + + + + +
+ + +When integrating with external configuration providers, it’s essential to consider security aspects, especially when dealing with sensitive configuration data. Use secure communication channels (e.g., HTTPS) to retrieve configuration from external services. Manage access control meticulously to prevent unauthorized access to sensitive configuration. Consider encrypting sensitive configuration values and decrypting them within your ConfigSource or application logic. +
+
+
+
+

Registering a ConfigSource

+
+

To register a custom ConfigSource implementation with MicroProfile Config, you need to include the fully qualified class name of your custom ConfigSource in this resource file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource.

+
+
+

This PaymentService would be a part of the e-Commerce application, handling payment transactions by utilizing configurations that determine which payment gateway to use and how to authenticate with it. By externalizing these configurations, the e-Commerce platform can easily switch payment providers or update API keys without needing to adjust the codebase, providing flexibility and enhancing security.

+
+
+
+

Accessing the Configuration Data

+
+

First, create a class to represent the payment information sent by clients as below:

+
+
+
+
package io.microprofile.tutorial.store.payment.entity;
+
+import java.math.BigDecimal;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PaymentDetails {
+   private String cardNumber;
+   private String cardHolderName;
+   private String expirationDate; // Format MM/YY
+   private String securityCode;
+   private BigDecimal amount;
+}
+
+
+
+

The PaymentDetails class succinctly encapsulates the necessary attributes for processing payments. This class can be used to pass payment details for processing payments, validating card details, and logging transaction information.

+
+
+

Next, implement the PaymentService class, which utilizes MicroProfile Config to inject the necessary configurations. It represents a simple service that could call a payment gateway API using the configurations provided by the custom ConfigSource.

+
+
+
+
import org.eclipse.microprofile.config.inject.ConfigProperty;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+
+@Path("/authorize")
+@RequestScoped
+public class PaymentService {
+
+    @Inject
+    @ConfigProperty(name = "payment.gateway.apiKey")
+    private String apiKey;
+
+    @Inject
+    @ConfigProperty(name = "payment.gateway.endpoint")
+    private String endpoint;
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response processPayment(PaymentDetails paymentDetails) {
+        // Example logic to call the payment gateway API
+        System.out.println("Processing payment with details: " + paymentDetails.toString());
+        System.out.println("Calling payment gateway API at: " + endpoint);
+        // Assuming a successful payment operation for demonstration purposes
+        // Actual implementation would involve calling the payment gateway and handling the response
+
+        // Dummy response for successful payment processing
+        String result = "{\"status\":\"success\", \"message\":\"Payment processed successfully.\"}";
+        return Response.ok(result, MediaType.APPLICATION_JSON).build();
+    }
+}
+
+
+
+

Explanation:

+
+
+
    +
  • +

    @Path("/authorize"): Defines the base URI for the RESTful service. This class will handle requests made to URIs that start with /payment/api/authorize.

    +
  • +
  • +

    @RequestScoped: Indicates that a new instance of PaymentService is created for each HTTP request.

    +
  • +
  • +

    @POST: Specifies that the processPayment method will respond to HTTP POST requests, which is appropriate for operations that change server state (in this case, processing a payment).

    +
  • +
  • +

    @Consumes(MediaType.APPLICATION_JSON): Indicates that the method expects requests to have a payload formatted as JSON, aligning with how payment details might be sent.

    +
  • +
  • +

    @Produces(MediaType.APPLICATION_JSON): Specifies that the method produces JSON-formatted responses, useful for indicating the result of the payment processing operation.

    +
  • +
  • +

    Response processPayment(PaymentDetails paymentDetails): The method now returns a Response object, allowing for more flexible HTTP response handling. The PaymentDetails parameter would be a POJO (Plain Old Java Object) representing the payment information sent by the client.

    +
  • +
+
+
+

The clients can call to process payments through the e-Commerce application using this RESTful web service endpoint. The actual logic for calling the payment gateway API and handling the response would be implemented within this method, utilizing the injected configuration properties for authentication and endpoint URL.

+
+
+

ConfigSources are hierarchical, which means that we can override properties from one ConfigSource with another ConfigSource. For example, we could create a ConfigSource that reads configuration data from a file, and another ConfigSource that reads configuration data from system properties. The system properties would take precedence over the file-based ConfigSource, which would take precedence over the default ConfigSource.

+
+
+
    +
  • +

    Property getProperty(String propertyName) : Returns information about the given property. If the property is not found, this method returns null.

    +
  • +
+
+
+
+

Enabling MicroProfile Config in Open Liberty

+
+

Open Liberty requires a server.xml file for server configuration. This file should be located at /src/main/liberty/config/server.xml within your project. To enable MicroProfile Config, you need to include the mpConfig feature in the <featureManager> section.

+
+
+
+
<server description="MicroProfile Tutorial Server">
+  <featureManager>
+    <feature>mpConfig-3.1</feature> <!-- Use the appropriate version -->
+    <!-- Include other features as needed -->
+  </featureManager>
+
+
+
+
+

Managing Configuration for Different Environments

+
+

Managing configurations for different environments is a crucial aspect of modern application development, especially in microservices architectures where applications may run in development, testing, staging, and production environments with varying configurations. MicroProfile Config provides the flexibility to handle environment-specific configurations efficiently. Here’s how to manage configurations for different environments using MicroProfile Config:

+
+
+

Use of Profiles: MicroProfile Config does not explicitly define the concept of profiles for managing environment-specific configurations. However, developers can implement a profile-like mechanism using custom ConfigSource implementations or by organizing configuration properties in a way that differentiates them by environment. For instance, you could prefix configuration keys with the environment name:

+
+
+
    +
  • +

    dev.database.url

    +
  • +
  • +

    test.database.url

    +
  • +
  • +

    prod.database.url

    +
  • +
+
+
+

Then, you can programmatically or conditionally load configurations based on the active environment.

+
+
+

Environment Variables and System Properties: Leveraging environment variables and system properties is a common and effective way to provide environment-specific configurations. MicroProfile Config automatically includes ConfigSources for both system properties and environment variables, allowing for easy overrides of configurations per environment:

+
+
+
+
String databaseUrl = ConfigProvider.getConfig().getValue("database.url", String.class);
+
+
+
+

Custom ConfigSources: For more complex scenarios or to integrate with external configuration management systems (e.g., Consul, Etcd, AWS Parameter Store), you can implement custom ConfigSources. These sources can dynamically load configurations based on the environment, either by connecting to external services or by loading environment-specific files:

+
+
+
+
public class MyEnvironmentConfigSource implements ConfigSource {
+    // Implementation that loads configurations based on the detected environment
+}
+
+
+
+

Configuration Isolation: It’s essential to isolate configurations for different environments to prevent accidental leaks of sensitive information (e.g., production database credentials). This can be achieved by using: +- separate configuration files for each environment, stored securely and only accessible by the application running in that environment. +- Utilizing external secrets management tools to store sensitive configurations, with access controlled by the environment.

+
+
+

CI/CD Integration: Integrate environment-specific configuration management into your CI/CD pipelines. Ensure that the correct configurations are applied automatically as part of the deployment process for each environment.

+
+
+
+

Strategies for Handling Configuration Change Events

+
+

Although direct support for configuration change events is not provided by MicroProfile Config specification itself, applications can implement their mechanisms or use external libraries to achieve this functionality. To implement dynamic updates in your MicroProfile Config usage, you might need to adopt one of the following approaches:

+
+
+
    +
  • +

    Manual Refresh: Provide a mechanism (e.g., an admin-restricted endpoint) to manually trigger a refresh of the configuration. This approach gives control over when changes are applied but requires manual intervention.

    +
  • +
  • +

    Polling: Implement a scheduler that periodically checks certain configuration properties for changes. This approach is straightforward but might introduce latency between the actual change and its detection.

    +
  • +
  • +

    Event-driven Updates: If your configuration source supports event notifications (for example, a database trigger or a cloud service event), you can set up listeners that update your application’s configuration in response to these events.

    +
  • +
  • +

    Application-level Event Handling: Design your application components to subscribe to a custom event bus or notification system. When a configuration change is detected (via polling or custom ConfigSource), publish an event to this bus, triggering subscribed components to update their configurations.

    +
  • +
  • +

    Custom Configuration Source: Develop a custom ConfigSource that includes logic to listen for changes in the underlying configuration store (such as a database, filesystem, or cloud service). This ConfigSource can then notify the application of changes, prompting it to refresh configuration properties.

    +
  • +
  • +

    Runtime Extensions: Some MicroProfile runtimes may offer extensions that support dynamic configuration and change event handling. Check the documentation of your runtime environment for such features and best practices for their usage.

    +
  • +
  • +

    Framework/Library Support: Use a third-party library or framework that extends MicroProfile Config with change event support. These libraries might offer annotations or listener interfaces to react to configuration changes automatically.

    +
  • +
  • +

    External Configuration Management Tools: Utilize configuration management tools or services that offer webhook or messaging functionalities to notify your application of configuration changes. Upon receiving a notification, the application can reload its configuration context.

    +
  • +
+
+
+

While MicroProfile Config provides the mechanisms to read from dynamic configuration sources, it does not specify a standard way to listen for changes in configuration properties directly within its API as of version 3.1. Applications need to implement their logic or use additional libraries/frameworks to detect changes in configuration sources and react accordingly.

+
+
+

However, some implementations of MicroProfile Config might offer extensions or additional functionalities to support configuration change events. For example, an application can poll a configuration source at intervals to detect changes or use a notification system that triggers configuration reloads.

+
+
+
+
+
+

Best Practices and Securing Configuration in MicroProfile Config

+
+
+

Here are some recommended practices for using MicroProfile Config:

+
+
+

Graceful Configuration Reloads: Ensure that your application can gracefully handle configuration reloads, especially in critical components that depend on configuration properties for their operation.

+
+
+

Minimize Performance Impact: Design your dynamic configuration update mechanism to minimize performance impacts, especially if using polling mechanisms.

+
+
+

Secure Configuration Management: When implementing custom solutions for dynamic configuration, pay attention to security aspects, particularly if configurations include sensitive information. Securing sensitive configuration properties is crucial for maintaining the security and integrity of applications.

+
+
+

Encrypt Sensitive Configuration Values: Sensitive information, such as passwords, tokens, and API keys, should be encrypted in the configuration source. Decryption can be handled programmatically within the application or through integration with external secrets management systems.

+
+
+

Use Environment-Specific Configuration Files: Separate configuration files for different environments (development, testing, production) can help minimize the risk of exposing sensitive data. For instance, development configurations might use placeholder values, whereas production configurations access secrets from a secure vault or environment variables.

+
+
+

Leverage External Secrets Management: Integrating with external secrets management tools (like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault) ensures that sensitive configurations are stored securely and accessed dynamically at runtime. These tools provide mechanisms to control access to secrets and often include auditing capabilities.

+
+
+

Use Environment Variables for Sensitive Values: Environment variables can be a secure way to provide configuration to applications, especially for containerized or cloud-native applications. This approach leverages the underlying platform’s security model to protect sensitive information.

+
+
+

Implement Access Control: Ensure that only authorized personnel have access to configuration files, especially those containing sensitive information. Use file permissions, access control lists (ACLs), or similar mechanisms provided by the operating system or hosting environment.

+
+
+

Audit and Monitor Configuration Access: Regularly audit access to configuration files and monitor for unauthorized access attempts. This can help detect potential security breaches and ensure that only authorized changes are made to the configuration.

+
+
+

Configuration Validation: Validate configuration data at startup to ensure that it meets the application’s expected format and values. This step can prevent configuration errors and detect tampering or unauthorized changes.

+
+
+

Keep Configuration Data Updated: Regularly review and update configuration data to ensure that it reflects the current operational and security needs. Remove unused properties and update secrets periodically to reduce the risk of compromise.

+
+
+
+
+

Summary

+
+
+

Dynamic configuration management is essential for modern applications, providing the flexibility to adapt to changing environments without downtime. Although MicroProfile Config as of version 3.1 does not define a standard for handling configuration change events directly, applications can still achieve this by combining MicroProfile Config with custom logic or additional tools designed for dynamic configuration management. Always consult the documentation of your MicroProfile implementation to learn about supported features and extensions related to dynamic configuration and change events.

+
+
+

While the MicroProfile Config specification provides a powerful and flexible framework for configuration management, handling dynamic updates and configuration change events may require additional custom development or the use of external tools. By considering the strategies mentioned above, developers can effectively manage configuration changes, ensuring their microservices remain responsive and resilient in dynamic environments.

+
+
+

The MicroProfile Config specification offers a robust and adaptable framework for managing application configurations. By implementing MicroProfile Config, developers can effectively manage configuration changes, ensuring their microservices remain responsive and resilient in dynamic environments.

+
+
+

Integrating external configuration providers with MicroProfile Config extends the flexibility and dynamism of configuration management in microservices architectures. By implementing custom ConfigSources, applications can seamlessly adapt to various environments and configuration paradigms, pulling configuration data from virtually any source.

+
+
+

Handling missing or invalid configurations in MicroProfile Config involves using default values, optional properties, custom ConfigSource implementations, and appropriate exception handling. By following these practices, you can ensure that your application remains robust and flexible, even in the face of configuration challenges.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter06/chapter06.html b/build/site/microprofile-tutorial/6.1/chapter06/chapter06.html new file mode 100644 index 00000000..b612a42c --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter06/chapter06.html @@ -0,0 +1,787 @@ + + + + + +MicroProfile Health :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Health

+
+

Introduction

+
+
+

This chapter provides an in-depth exploration of MicroProfile Health, a critical component for ensuring the reliability and +availability of microservices. This specification aims to enhance the observability of microservices in a cloud environment +where automatic scaling, failover, and recovery are essential for maintaining service availability and reliability. In this +chapter, we will learn about different types of health checks and standard health indicators provided by MicroProfile.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Overview of MicroProfile Health

    +
  • +
  • +

    Key Concepts

    +
  • +
  • +

    Types of Health Checks

    +
  • +
  • +

    Exposing Health Checks

    +
  • +
  • +

    Steps for Implementing Health Checks

    +
  • +
  • +

    Integration with CDI

    +
  • +
  • +

    Accessing Health Checks

    +
  • +
  • +

    Kubernetes Probe Configuration

    +
  • +
  • +

    Best Practices for Effective Health Checks

    +
  • +
+
+
+
+
+

Overview of MicroProfile Health

+
+
+

The MicroProfile Health specification offers a standardized mechanism for microservices to report their health status. In the +context of microservices, "health" refers to the ability of a microservice to perform its functions correctly and efficiently. +The health check mechanism is crucial for operating microservices in a cloud or containerized environment where automated processes +need to make decisions about whether to restart a failing service, reroute traffic away from an unhealthy service, or take +other actions to maintain overall system reliability.

+
+
+

Let’s delve into the essentials of MicroProfile Health, its importance, and how it works.

+
+
+
+
+

Key Concepts

+
+
+

At its core, the MicroProfile Health specification defines a mechanism for microservices to report their health status via HTTP. +These health checks can be used by external systems to verify the operational status of the services. This is crucial in modern cloud +environments where automated processes continuously monitor service health, initiate failover procedures, and manage load balancing +to ensure high availability and reliability.

+
+
+

Health Check

+
+

A health check is a test that can be used to determine the health of an application or service. This mechanism is implemented via +standard HTTP endpoints that respond with the health status of the service. These endpoints are typically exposed at predefined paths, +such as /health, /health/live (for liveness), /health/ready (for readiness), and /health/started (for startup). Health status is +communicated through a simple JSON format, which can be easily interpreted by humans and machines. Applications servers that support +MicroProfile may offer built-in mechanisms or simplified configurations to define such health checks.

+
+
+
+
+
+

Types of Health Checks

+
+
+

MicroProfile Health Check defines three main types of health checks, each with its own annotation to indicate the MicroProfile Health +runtime about the type of check being performed, allowing it to execute and report health check responses appropriately. These are:

+
+
+

Liveness Checks

+
+

Liveness checks help to determine if a microservice is in a state where it can perform its functions correctly. A failing liveness +check suggests that the microservice is in a broken state, and the only way to recover might be to restart the microservice. This + type of health check is crucial for detecting deadlocks, infinite loops, or any conditions that render the microservice unresponsive +or dysfunctional. Liveness checks are annotated with @Liveness.

+
+
+
+

Readiness Checks

+
+

Readiness checks are used to determine if a microservice is ready to process requests. If a readiness check fails, it indicates that +the microservice should not receive any inbound requests because it’s not ready to handle them properly. This can be due to the +application still initializing, waiting for dependencies, or any other condition that would prevent it from correctly processing +incoming requests. Readiness checks are annotated with @Readiness.

+
+
+
+

Startup Checks

+
+

Startup checks are designed for verifying the microservice’s health immediately after it has started. This type of check is useful +for applications that require additional initialization time or need to perform certain actions before they are ready to serve requests. +Including startup checks in the health checking mechanism is crucial because if we hit the liveness probe before the application is fully +initialized, it could cause a continuous restart loop. Startup checks provide a mechanism to postpone other health checks until certain +startup conditions are fulfilled. This ensures that readiness and liveness probes are not prematurely activated, allowing the microservice +adequate time to complete its initialization processes, such as loading configurations, establishing database connections, or performing +necessary pre-service tasks.These checks are annotated with @Startup.

+
+
+
+
+
+

Exposing Health Checks

+
+
+

Health checks are exposed via HTTP endpoints automatically without additional configuration needed from the developer’s side. The runtime +environment provides these endpoints:

+
+
+
    +
  • +

    /health: Aggregates all health check responses.

    +
  • +
  • +

    /health/live: Returns responses from liveness checks.

    +
  • +
  • +

    /health/ready: Returns responses from readiness checks.

    +
  • +
  • +

    /health/started: Returns responses from startup checks.

    +
  • +
+
+
+

These endpoints return a JSON object containing the overall status (UP or DOWN) and individual health check responses, including their names, +statuses, and optional data.

+
+
+

Example JSON Response

+
+

For example a LivenessCheck, if accessed via /health/live, the JSON response might look something like this when the service is healthy:

+
+
+
+
{
+  "status": "UP",
+  "checks": [
+    {
+      "name": "LivenessCheck",
+      "status": "UP"
+    }
+  ]
+}
+
+
+
+

If the service is unhealthy, the "status" field would be "DOWN", and additional data might be provided to indicate the cause of the health check failure. +Each type of health check is implemented as a procedure annotated with the respective annotation. Each procedure returns a HealthCheckResponse indicating +the health status (UP or DOWN) and optionally includes additional details. Implementing these health check types in microservices architecture ensures +that services are only used when they are in a healthy state and can correctly process requests. This enhances the overall reliability and +maintainability of applications.

+
+
+
+

Standard Health Check

+
+

Applications can implement multiple health checks of each kind. The overall health status reported by the application is a logical AND of all individual +health checks. A special endpoint /health aggregates the results from all three types of checks.

+
+
+
+

Implementing and Exposing Health Check

+
+

To implement health checks for microservices using MicroProfile Health, you would generally follow a pattern to define health check procedures that align with the services' operational characteristics. The Health Check API allows us to expose information about the health of our application. This information can be used by load balancers and other tools to determine if an application is healthy.

+
+
+
+

The HealthCheck interface

+
+

The HealthCheck functional interface uses CDI beans with annotations (@Liveness, @Readiness, and, @Startup) to mark a class as a health checker for liveness, readiness and startup. They are automatically discovered and registered by the runtime. Implementations of this interface are expected to be provided by applications.

+
+
+

The Health Check API defines a contract for health check implementations. A health check is a Java class that implements the HealthCheck functional interface:

+
+
+
+
package org.eclipse.microprofile.health;
+
+@FunctionalInterface
+public interface HealthCheck {
+  HealthCheckResponse call();
+}
+
+
+ +
+
+

The HealthCheckResponse class

+
+

The HealthCheckResponse class is used to represent the result of a health check invocation. It contains information about the health check, such as name, state (up or down), and data that can be used for troubleshooting.

+
+
+

The call() method of HealthCheck interface is used to perform the actual health check and return a HealthCheckResponse object:

+
+
+
+
package org.eclipse.microprofile.health;
+
+public class HealthCheckResponse {
+
+   private static final Logger LOGGER =    Logger.getLogger(HealthCheckResponse.class.getName());
+
+    // the name of the health check.
+    private final String name;
+
+    // the outcome of the health check
+    private final Status status;
+
+    // information about the health check.
+    private final Optional<Map<String, Object>> data;
+
+    // Status enum definition
+    public enum Status {
+        UP, DOWN
+    }
+
+   // Getters
+    public String getName() {
+        return name;
+    }
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public Optional<Map<String, Object>> getData() {
+        return data;
+    }
+
+}
+
+
+
+

The provided code snippet offers a conceptual and simplified implementation of the HealthCheckResponse class to illustrate how health check responses can be structured within the MicroProfile Health framework. To view the actual HealthCheckResponse class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponse.java

+
+
+
+

The HealthCheckResponseBuilder class

+
+

The HealthCheckResponseBuilder abstract class provides a fluent API for constructing instances of HealthCheckResponse. This means you can chain method calls to set various properties of the response in a single statement, improving code readability and maintainability.

+
+
+
+
package org.eclipse.microprofile.health;
+
+public abstract class HealthCheckResponseBuilder {
+
+    // Sets the name of the health check response.
+    public abstract HealthCheckResponseBuilder name(String name) {
+        this.name = name;
+    }
+
+    // Sets the status of the health check to UP
+    public abstract HealthCheckResponseBuilder up();
+
+    // Sets the status of the health check to DOWN
+    public abstract HealthCheckResponseBuilder down();
+
+    // Adds additional string data to the health check response
+    public HealthCheckResponseBuilder withData(String key, String value);
+
+    // Adds additional numeric data to the health check response
+    public HealthCheckResponseBuilder withData(String key, long value);
+
+    // Sets the status of the health check response
+    public abstract HealthCheckResponseBuilder status(boolean up);
+
+    // Builds and returns the HealthCheckResponse instance
+    public abstract HealthCheckResponse build();
+
+}
+
+
+
+

The above code snippet offers a conceptual and simplified definition of the HealthCheckResponseBuilder abstract class to illustrate how health check responses can be structured within the MicroProfile Health framework. For the actual HealthCheckResponseBuilder abstract class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponseBuilder.java

+
+
+
+
+
+

Steps for Implementing Health Checks

+
+
+

Below are the steps for implementing Health Checks for each of the microservices:

+
+
+

Add MicroProfile Health Dependency: To utilize MicroProfile Health in a Java project, include the MicroProfile Health API dependency in your pom.xml or build.gradle file.

+
+
+

For maven, add:

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.health</groupId>
+  <artifactId>microprofile-health-api</artifactId>
+  <version>4.0.1</version>
+</dependency>
+
+
+
+

For gradle, add:

+
+
+
+
implementation 'org.eclipse.microprofile.health:microprofile-health-api:4.0.1'
+
+
+
+ + + + + +
+ + +When implementing MicroProfile Health checks, including the MicroProfile Health API dependency in your project is not enough. You need an actual implementation on the classpath. This could be a MicroProfile-compatible server runtime such as Open Liberty, Quarkus, Payara Micro, or WildFly. Without an implementation present at runtime, the application will not be able to execute health checks. +
+
+
+

The health information can be used by other tools to help keep our application running well.

+
+
+

Implementing Health Checks

+
+

Health checks in MicroProfile are implemented as CDI beans that implement the HealthCheck interface. Each health check procedure is a method that returns a HealthCheckResponse. You can define different types of health checks (readiness, liveness, and startup) depending on the type of check by annotating the health check class with @Readiness, @Liveness, or @Startup. These methods return a HealthCheckResponse object, which includes the health check status (UP or DOWN) and additional metadata about the health check.

+
+
+

Readiness Check:

+
+
+
+
package io.microprofile.tutorial.store.product.health;
+
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Readiness;
+
+import io.microprofile.tutorial.store.product.entity.Product;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+
+@Readiness
+@ApplicationScoped
+public class ProductServiceHealthCheck implements HealthCheck {
+
+    @PersistenceContext
+    EntityManager entityManager;
+
+    @Override
+    public HealthCheckResponse call() {
+        if (isDatabaseConnectionHealthy()) {
+            return HealthCheckResponse.named("ProductServiceReadinessCheck")
+                    .up()
+                    .build();
+        } else {
+            return HealthCheckResponse.named("ProductServiceReadinessCheck")
+                    .down()
+                    .build();
+        }
+    }
+
+    private boolean isDatabaseConnectionHealthy(){
+        try {
+            // Perform a lightweight query to check the database connection
+            entityManager.find(Product.class, 1L);
+            return true;
+        } catch (Exception e) {
+            System.err.println("Database connection is not healthy: " + e.getMessage());
+            return false;
+        }
+    }
+}
+
+
+
+

Liveness Check:

+
+
+
+
package io.microprofile.tutorial.store.product.health;
+
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
+import org.eclipse.microprofile.health.Liveness;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@Liveness
+@ApplicationScoped
+public class ProductServiceLivenessCheck implements HealthCheck {
+
+   @Override
+   public HealthCheckResponse call() {
+       Runtime runtime = Runtime.getRuntime();
+       long maxMemory = runtime.maxMemory(); // Maximum amount of memory the JVM will attempt to use
+       long allocatedMemory = runtime.totalMemory(); // Total memory currently allocated to the JVM
+       long freeMemory = runtime.freeMemory(); // Amount of free memory within the allocated memory
+       long usedMemory = allocatedMemory - freeMemory; // Actual memory used
+       long availableMemory = maxMemory - usedMemory; // Total available memory
+
+       long threshold = 100 * 1024 * 1024; // threshold: 100MB
+
+     	 // Including diagnostic data in the response
+       HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("systemResourcesLiveness")
+            .withData("FreeMemory", freeMemory)
+            .withData("MaxMemory", maxMemory)
+            .withData("AllocatedMemory", allocatedMemory)
+            .withData("UsedMemory", usedMemory)
+            .withData("AvailableMemory", availableMemory);
+
+        if (availableMemory > threshold) {
+            // The system is considered live
+            responseBuilder = responseBuilder.up();
+        } else {
+            // The system is not live.
+            responseBuilder = responseBuilder.down();
+        }
+
+        return responseBuilder.build();
+    }
+}
+
+
+
+

The above code uses the HealthCheckResponseBuilder to construct the response. Depending on the outcome of checkDatabaseConnection(), the health check response is marked either "up" or "down", and relevant data is added to the response using .withData(key, value). This approach allows for rich, descriptive health check responses that can convey detailed status information, not just binary up/down states.

+
+
+

Startup Check:

+
+
+
+
package io.microprofile.tutorial.store.product.health;
+
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+import jakarta.ejb.Startup;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.PersistenceUnit;
+
+@Startup
+@ApplicationScoped
+public class ProductServiceStartupCheck implements HealthCheck{
+
+    @PersistenceUnit
+    private EntityManagerFactory emf;
+
+    @Override
+    public HealthCheckResponse call() {
+        if (emf != null && emf.isOpen()) {
+            return HealthCheckResponse.up("ProductServiceStartupCheck");
+        } else {
+            return HealthCheckResponse.down("ProductServiceStartupCheck");
+        }
+    }
+}
+
+
+
+
+
+
+

Integration with CDI

+
+
+

The specification also emphasizes the importance of integrating health checks with the application’s Context and Dependency Injection (CDI) context, enabling health check procedures to be automatically discovered and invoked by the runtime. +MicroProfile Health thus provides a robust and standardized way to implement health checks, facilitating the management and orchestration of microservices in a cloud environment.

+
+
+
+
+

Accessing Health Checks

+
+
+

Once defined, these health check procedures are automatically discovered and invoked by the MicroProfile Health runtime. They are accessible through standardized HTTP endpoints provided by MicroProfile Health (/health, /health/live, /health/ready, /health/started) and can be used by orchestration tools (like Kubernetes) or monitoring systems to manage and monitor the health of your microservices.

+
+
+

This approach allows you to tailor health checks to the operational specifics of each microservice, providing a robust mechanism for observing and managing your application’s health in a cloud-native environment.

+
+
+
+
+

Kubernetes Probe Configuration

+
+
+

Integrating MicroProfile Health checks with Kubernetes probes allows you to leverage Kubernetes' native capabilities to manage the lifecycle of your containers based on their current health status. Specifically, you can map Liveness, Readiness, and Startup probes in Kubernetes to the corresponding health check types defined by the MicroProfile Health specification.

+
+
+

Here’s a basic overview of how each type of MicroProfile Health check maps to Kubernetes probes:

+
+
+
    +
  • +

    Liveness Probes: Determine if a container is running and healthy. If a liveness probe fails, Kubernetes will kill the container and create a new one based on the restart policy.

    +
  • +
  • +

    *Readiness Probes: Determine if a container is ready to serve traffic. If a readiness probe fails, Kubernetes will stop sending traffic to that container until it passes again.

    +
  • +
  • +

    Startup Probes: Determine if a container application has started. These are useful for applications that have a long startup time to prevent them from being killed by Kubernetes before they are up and running.

    +
  • +
+
+
+

To configure these probes in your Kubernetes pod, you can use the livenessProbe, readinessProbe, and startupProbe fields in your container specification. Here’s an example of how you might define a readiness probe in your Kubernetes pod configuration, that utilizes a MicroProfile Health endpoint:

+
+
+
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: mp-pod
+spec:
+  containers:
+  - name: my-mp-app
+    image: myimage:v1
+ports:
+- containerPort: 8080
+readinessProbe:
+  httpGet:
+    path: /health/ready
+    port: 8080
+  initialDelaySeconds: 15
+  timeoutSeconds: 2
+  periodSeconds: 5
+  failureThreshold: 3
+
+
+
+

In the above example, the readinessProbe is configured to make an HTTP GET request to the /health/ready endpoint, which is the default endpoint provided by MicroProfile Health for readiness checks. Similarly, you can configure livenessProbe and startupProbe by specifying /health/live and /health/startup endpoints respectively.

+
+
+

It’s important to adjust the initialDelaySeconds, timeoutSeconds, periodSeconds, and failureThreshold according to the specifics of your application to ensure that Kubernetes accurately reflects the state of your containers based on its health checks.

+
+
+
+
+

Best Practices for Effective Health Checks

+
+
+

Here are some best practices for implementing and utilizing health checks effectively:

+
+
+
    +
  • +

    Clearly Define Health Check Types: Use readiness, liveness, and startup checks appropriately to reflect the state of your microservices. This helps in accurately signaling the service’s ability to handle traffic and its current operational state.

    +
  • +
  • +

    Implement Meaningful Health Checks: Ensure that your health checks meaningfully reflect the operational aspects they are intended to monitor. Avoid trivial checks that do not accurately represent the service’s health.

    +
  • +
  • +

    Utilize Health Check Responses: Make effective use of the health check responses, including the UP/DOWN status and additional metadata. This information can be valuable for logging and reporting on the health state of your services.

    +
  • +
  • +

    Secure Health Check Endpoints: Consider the security of your health check endpoints, especially if they expose sensitive details about the application’s state.

    +
  • +
  • +

    Monitor Health Check Performance: Health checks should be lightweight and not introduce significant overhead. Monitor the performance of your health checks and optimize as needed to prevent impacting the application’s performance.

    +
  • +
  • +

    Logging Health Check Results: Implementing logging within your health check procedures can provide insights into the health status over time. Log entries can be made when health check statuses change or when significant health-related events occur.

    +
  • +
  • +

    Reporting and Alerting: Based on logged health check results, implement reporting mechanisms to visualize the health over time and set up alerting for when health checks fail. This could be integrated with existing monitoring and alerting tools.

    +
  • +
+
+
+

By following these best practices, you can effectively implement and expose health checks in your MicroProfile applications, improving observability and reliability, especially in cloud-native environments.

+
+
+
+
+

Summary

+
+
+

This chapter provided a comprehensive overview of MicroProfile Health, emphasizing its critical role in enhancing the observability and reliability of microservices within cloud environments. Key topics included an introduction to the MicroProfile Health specification, detailed explanations of health check types (liveness, readiness, and startup checks), and guidance on implementing, exposing, and effectively utilizing these health checks.

+
+
+

The essence of MicroProfile Health lies in its standardized mechanism for microservices to report health status via HTTP endpoints, facilitating automated decision-making processes like scaling, failover, and recovery in cloud or containerized environments. The specification defines three primary types of health checks: liveness, readiness, and startup checks, each designed to assess different aspects of a microservice’s operational status.

+
+
+

Implementing health checks involves creating procedures annotated with the respective health check annotations. These procedures return a HealthCheckResponse indicating the service’s health status (UP or DOWN). These checks are automatically exposed via predefined HTTP endpoints, allowing easy integration with orchestration tools like Kubernetes.

+
+
+

The chapter also touched on best practices for effective health checks, including defining meaningful checks, utilizing health check responses, handling failures gracefully, and securing health check endpoints. In conclusion, MicroProfile Health offers a robust framework for monitoring and managing the health of microservices, ensuring that services remain reliable and available in dynamic cloud environments. By following the guidelines and best practices outlined in this chapter, developers can effectively implement and leverage health checks to maintain the overall health of their applications.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter07/chapter07.html b/build/site/microprofile-tutorial/6.1/chapter07/chapter07.html new file mode 100644 index 00000000..b65b6df8 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter07/chapter07.html @@ -0,0 +1,554 @@ + + + + + +MicroProfile Metrics :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Metrics

+
+

Introduction

+
+
+

This chapter provides a comprehensive and detailed overview of MicroProfile Metrics, a widely used specification for monitoring microservices. You will gain an understanding of the various types of metrics and how you can use them to monitor microservices effectively. Additionally, this chapter covers the standard metrics provided by MicroProfile and how you can leverage them to monitor various aspects of microservices.

+
+
+

Furthermore, this chapter discusses the process of instrumenting microservices, which involves adding code to the application to collect metrics. You will learn how to expose endpoints to access metric data and interpret the data generated by these metrics.

+
+
+

This chapter also highlights the importance of integrating monitoring solutions with MicroProfile Metrics. You will learn how to incorporate monitoring solutions and choose the right monitoring solution for your needs.

+
+
+

By the end of this chapter, you will have a deep understanding of MicroProfile Metrics and the various techniques for monitoring microservices. This chapter will equip you with the knowledge and skills to effectively monitor your microservices and ensure they perform optimally.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Introduction to MicroProfile Metrics

    +
  • +
  • +

    Need for Metrics in Microservices

    +
  • +
  • +

    Types of Metrics

    +
  • +
  • +

    MicroProfile Metrics Dependency

    +
  • +
  • +

    Metrics Annotations

    +
  • +
  • +

    Categories of Metrics

    +
  • +
  • +

    Metric Registry

    +
  • +
  • +

    Instrumenting Microservices with Metrics

    +
  • +
  • +

    Creating Custom Metrics

    +
  • +
+
+
+
+
+

Introduction to MicroProfile Metrics

+
+
+

It is essential to monitor your microservices to ensure smooth operations. You can monitor a microservice using two different techniques: Metrics and health checking. Health checks provide information on the health status of a service, such as whether it is up and running, while Metrics offer more detailed information on its performance, such as response times, throughput, and error rates. In the previous chapter, we discussed health checks and their importance. This chapter will cover the MicroProfile Metrics specification, which provides a standardized way of collecting and exposing performance data for Java microservices.

+
+
+

MicroProfile Metrics is a specification for developers who want to measure their applications' performance more thoroughly. It provides a set of annotations and APIs to track various metrics related to the application’s health and performance. For instance, developers can use these APIs to track metrics such as the number of requests processed, the response time of each request, and the size of the response sent back to the client.

+
+
+

This specification defines a standardized format for exposing metrics, which other tools and frameworks can easily collect and track. By using this specification, developers can monitor the performance of their applications in real time and identify any issues that may impact the user experience.

+
+
+

Moreover, this specification defines a set of standard metrics that we can expose in Prometheus. With the help of this tool, developers can optimize their applications for better performance, ensuring that they meet the requirements of their consumers while delivering a seamless experience.

+
+
+

Prometheus is a powerful tool designed to monitor and collect metrics from your services. It provides a highly efficient time-series database system that securely stores your data for long-term analysis. With Prometheus, you can easily visualize and gain insights into your system’s performance, allowing you to make informed decisions and optimize your services for better efficiency and reliability.

+
+
+
+
+

Need for Metrics in Microservices

+
+
+

Metrics, enables developers and operators to monitor and measure the behavior of microservices at runtime. This observability is crucial for:

+
+
+
    +
  • +

    Performance Tuning: Identifying bottlenecks and optimizing resource utilization to ensure services are running efficiently.

    +
  • +
  • +

    Scalability: Making informed decisions on when to scale services up or down based on real-time data on load and performance.

    +
  • +
  • +

    Troubleshooting: Quickly pinpointing issues by analyzing trends in performance metrics, leading to reduced downtime.

    +
  • +
  • +

    Service Health Monitoring: Complementing the MicroProfile Health checks by providing deeper insights into the internal state of a service, beyond simple up/down statuses.

    +
  • +
+
+
+
+
+

Types of Metrics

+
+
+

MicroProfile Metrics offers a range of customizable metrics that can be used to measure and monitor microservices' performance. It allows developing microservices that are observable, manageable, and which provide insights into their behavior.

+
+
+

The MicroProfile Metrics specification includes four different types of metrics that serve specific monitoring purposes: Counter, Gauge, Histogram, and Timer. Each of these types offers unique insights into different aspects of application behavior and performance. Below is the breakdown of available metric types:

+
+
+
    +
  1. +

    Counter: It is a simple metric type that represents a single numerical value that can only increase over time. This metric is typically used to count occurrences of certain events, such as the number of requests processed, items created, or tasks completed. Monitoring tools like Prometheus are commonly used to analyze changes in the Counter’s value over specific intervals. These tools can track the differences in the Counter’s value across time periods, providing insights into the rate of occurrences and trends.

    +
  2. +
  3. +

    Gauge: It is a metric type that measures an instantaneous value of something , which can arbitrarily go up or down. It’s used to capture the value of a metric at a particular point in time like the size of a queue, memory usage, or current number of active user sessions. Gauges are typically used for values that change over time, providing a current "gauge" of the system’s state.

    +
  4. +
  5. +

    Histograms: They provide a distribution of values for a given metric, which are useful for identifying performance outliers. It measures the frequency of values in different ranges (or "buckets") and is useful for tracking the distribution of values, such as response times or data sizes. Histograms can give insights into the average, percentiles, and trends of the measured data over time.

    +
  6. +
  7. +

    Timer: It is a specialized metric type that aggregates timing durations and provides data such as the count, total time, mean, and maximum duration. It can also report the duration distribution. Timers are invaluable for tracking the duration of certain activities or operations within your application, such as processing time or method execution time.

    +
  8. +
+
+
+

Note: Counters, Histograms, and Timers, which are updated synchronously when annotations or API calls are made to update them, Gauges are registered as callbacks. These callbacks are invoked to retrieve their value at the moment the list of metrics is requested, typically by a monitoring tool calling the /metrics endpoint. This allows Gauges to provide a real-time snapshot of dynamic values as they fluctuate.

+
+
+

By leveraging these metrics, developers and operators can gain a deeper understanding of how their microservices are performing. They can use this information to identify areas where improvements can be made and optimize their microservices' performance.

+
+
+
+
+

MicroProfile Metrics Dependency

+
+
+

If you’re using Maven, add the following dependency to your pom.xml file located in the root folder of your project:

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.metrics</groupId>
+  <artifactId>microprofile-metrics-api</artifactId>
+  <version>5.1.1</version>
+</dependency>
+
+
+
+

For Gradle, add the corresponding dependency to your build.gradle file located within the root folder of your project:

+
+
+
+
dependencies {
+    providedCompile 'org.eclipse.microprofile.metrics:microprofile-metrics-api:5.1.1'
+}
+
+
+
+
+
+

Metrics Annotations

+
+
+

MicroProfile Metrics defines a set of annotations to be used for exposing metrics. These annotations can be used on classes, methods, or fields. Table 7-1 shows the list of Metrics Annotation along with their descriptions.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + +
AnnotationDescription

@Timed

Times how long a method takes to execute and exposes this information as a metric.

@Counted

Tracks how many times a method is invoked and exposes this information as a metric.

@Gauge

Enables you to expose a custom metric that can be any value. It is useful for exposing application-specific metrics.

+
+

Besides annotations, MicroProfile Metrics also defines a set of programmatic APIs for working with metrics. These APIs can be used to register custom metrics or access existing metrics.

+
+
+
+
+

Categories of Metrics

+
+
+

In MicroProfile Metrics, metrics are organized into three distinct scopes: Base, Vendor, and Application. This categorization is designed to clearly separate metrics by their origin and relevance, making it easier for developers and operators to monitor and manage the performance of their microservices. Each scope serves a specific purpose and contains a different set of metrics:

+
+
+
    +
  • +

    Base Metrics are common to all applications, such as the number of CPUs or the amount of free memory. These metrics provide essential information about the underlying Java Virtual Machine (JVM) and the core libraries that are common across all MicroProfile applications. Base metrics typically include JVM-specific metrics such as memory usage, CPU load, thread counts, and garbage collection statistics. The intention behind base metrics is to offer a consistent set of low-level metrics that are universally applicable and useful for monitoring the health and performance of the JVM itself, which is the foundation upon which all MicroProfile applications run. +Base metrics are exposed under the path /metrics?scope=base.

    +
  • +
  • +

    Application Metrics are specific to an application, they are defined by the developers of the MicroProfile applications themselves. These are custom metrics that are specific to the business logic or operational aspects of the application. Developers use annotations or programmatic APIs to create and register these metrics, tailoring them to monitor the performance and behavior of their application’s unique functionalities. Application metrics enable developers to gain insights into the runtime characteristics of their application, such as the number of transactions processed, response times for specific endpoints, or the rate of specific business events. +Application metrics are exposed under the path /metrics?scope=application.

    +
  • +
  • +

    Vendor Metrics are specific to a particular vendor or technology. These metrics provide insights into the performance and behavior of the runtime’s internal components and extensions. Since different MicroProfile implementations may offer additional features or optimize certain areas differently, vendor metrics can vary widely between runtimes. They allow runtime vendors to expose unique metrics that are relevant to their implementation, offering users the ability to monitor vendor-specific aspects of their applications. +Application metrics are exposed under the path /metrics?scope=vendor.

    +
  • +
+
+
+

Besides the standard scopes above, MicroProfile Metrics also supports custom scopes. You can use custom scopes to group sets of metrics that you frequently expect to view together.

+
+
+

Note: In version 5.x, base metrics have become optional. This allows for flexibility in environments where these metrics may not be necessary or where they can be sourced from alternative monitoring tools.

+
+
+
+
+

Metric Registry

+
+
+

The MetricRegistry component acts as a container for storing and managing metrics within an application. It provides a structured way to collect, organize, and access various types of metrics (e.g., counters, gauges, histograms, and timers) for monitoring the behavior and performance of applications. It offers a centralized repository where metrics can be created and retrieved. This allows applications to consistently monitor critical operational and performance statistics.

+
+
+

Types of Metric Registries

+
+

MicroProfile Metrics creates metric registries for each scope:

+
+
+
    +
  • +

    Application Scope (MetricRegistry.Type.APPLICATION): Contains custom metrics that are specific to the application. These are typically the metrics that developers explicitly create and register to monitor application-specific behaviors.

    +
  • +
  • +

    Base Scope (MetricRegistry.Type.BASE): Contains metrics that are fundamental and common across all MicroProfile applications. These metrics provide basic information about the underlying JVM and application server.

    +
  • +
  • +

    Vendor Scope (MetricRegistry.Type.VENDOR): Contains metrics that are specific to the implementation of the MicroProfile platform being used. These metrics offer insights into vendor-specific features and optimizations.

    +
  • +
+
+
+

A metric registry is created as per the above scopes to enable the organization of metrics based on their origin and relevance.

+
+
+
+

Instrumenting Microservices with MicroProfile Metrics

+
+

Instrumenting microservices with MicroProfile Metrics enables developers to gain detailed insights into their application’s operational health and performance. This level of observability is essential for maintaining scalable and resilient microservice architectures in dynamic environments.

+
+
+
+

Tracking response time using @Timed

+
+

MicroProfile Metrics also allows you to track a method’s response time as a timed metric. The code example below shows how to use the @Timed annotation to track the response time.

+
+
+
+
import org.eclipse.microprofile.metrics.annotation.Timed;
+// …
+
+public class ProductResource {
+
+    // …
+    // Expose the response time as a timer metric
+    @Timed(name = "productLookupTime",
+            tags = {"method=getProduct"},
+            absolute = true,
+            description = "Time spent looking up products")
+    public Product getProduct(@PathParam("id") Long productId) {
+        return productService.getProduct(productId);
+    }
+
+    // …
+
+
+
+

It will expose a metric called productLookupTime, which will track the amount of time spent in the getProduct() method in seconds. +You can visit the following URL https://localhost:<port>/metrics?scope=application (Replace <port> with the actual port where the server is running) to see the response time of this method as below:

+
+
+
+
…
+# HELP productLookupTime_seconds_max Time spent looking up products
+# TYPE productLookupTime_seconds_max gauge
+productLookupTime_seconds_max{method="getProduct",mp_scope="application",} 0.002270643
+…
+
+
+
+
+

Tracking number of invocations using @Counted

+
+

MicroProfile Metrics also allows you to track the number of invocations of a method as a counter metric. The code example below shows how to use the @Counted annotation to track the invocation count.

+
+
+
+
import org.eclipse.microprofile.metrics.Metrics;
+
+public class ProductResource {
+
+    // Expose the invocation count as a counter metric
+    @Counted(name = "productAccessCount",
+        absolute = true,
+        description = "Number of times the list of products is requested")
+    public Response getProducts() {
+       // Method implementation
+       // ....
+    }
+}
+
+
+
+

In the example above, the @Counted annotation tells MicroProfile Metrics to track the number of invocations of the getProducts() method and expose this metric as a counter. The name, and description of the metric can also be specified. +You can visit the following URL https://localhost:<port>/metrics?scope=application (Replace <port> with the actual port where the server is running) to see the number of times this method is called as below:

+
+
+
+
…
+# HELP productAccessCount_total Number of times the list of products is requested
+# TYPE productAccessCount_total counter
+productAccessCount_total{mp_scope="application",} 3.0
+…
+
+
+
+
+
+
+

Creating a Custom Metric

+
+
+

Creating a custom metric to track the number of products in a catalog involves using the MicroProfile Metrics API. This custom metric can be implemented as a gauge, which measures an instantaneous value (in this case, the current number of products in the catalog).

+
+
+
+
import org.eclipse.microprofile.metrics.annotation.Gauge;
+…
+
+@Path("/products")
+@ApplicationScoped
+public class ProductResource {
+  // …
+
+  @GET
+  @Path("/count")
+  @Produces(MediaType.APPLICATION_JSON)
+  @Gauge(name = "productCatalogSize",
+    unit = "none",
+  description = "Current number of products in the catalog")
+  public long getProductCount() {
+     return productCatalogSize;
+  }
+}
+
+
+
+

The gauge metric productCatalogSize can be accessed through the following endpoint:

+
+
+

/metrics?name=io_microprofile_tutorial_store_product_resource_ProductResource_productCatalogSize

+
+
+

This custom metric implementation provides a real-time insight into the size of your product catalog, which can be invaluable for monitoring the scale of your service’s data and understanding its behavior over time.

+
+
+

Vendors may, by their own implementation, support /metrics?name=<name> to directly retrieve that metric from all scopes. However, the specification itself only illustrates /metrics?scope=<scope>&name=<name>.

+
+
+
+
+

Summary

+
+
+

This Chapter delved into the intricacies of MicroProfile Metrics, illuminating its role as a pivotal specification for efficiently monitoring microservices. Now you are equipped with a thorough understanding of diverse metric types and their application for monitoring microservice performance. This chapter highlighted the need for regular microservice monitoring via metrics and health checks, emphasizing metrics for detailed performance insights such as response times and throughput. Through practical examples, this chapter showcases how to instrument microservices with MicroProfile Metrics, leveraging standard metrics, and creating custom metrics to monitor microservices comprehensively.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter08/chapter08.html b/build/site/microprofile-tutorial/6.1/chapter08/chapter08.html new file mode 100644 index 00000000..d7311ae1 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter08/chapter08.html @@ -0,0 +1,1473 @@ + + + + + +MicroProfile Fault Tolerance :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Fault Tolerance

+
+
+
+

In a Microservices architecture, an application consists of multiple smaller, autonomous services. This architecture enhances development flexibility, agility, and scalability but introduces new challenges, particularly in ensuring the application’s reliability and managing failures. Unlike monolithic applications, where defects are localized, a single failure in one microservice can propagate across the entire application, potentially causing widespread outages. Therefore, fault tolerance is critical in a microservices architecture to ensure that failures are seamlessly isolated, managed, and recovered.

+
+
+

MicroProfile Fault Tolerance offers strategies for building resilient and reliable microservices, ensuring service continuity and stability even during unexpected failures.

+
+
+

This chapter explains how to enhance your microservices' resilience and reliability using MicroProfile Fault Tolerance capabilities and annotations. We will also demonstrate how to implement key strategies such as timeouts, retries, fallbacks, circuit breakers, and bulkheads to handle faults. By the end of the chapter, you will understand how to use these strategies to enhance the resilience of your microservices.

+
+
+
+
+

Topics to be Covered

+
+
+
    +
  • +

    What is Fault Tolerance?

    +
  • +
  • +

    Key Strategies for Enhancing Fault Tolerance

    +
  • +
  • +

    Implementing Retry Policies and Configuration

    +
  • +
  • +

    Avoiding and Managing Cascading Failures

    +
  • +
  • +

    Configuring Circuit Breaker

    +
  • +
  • +

    Using @Asynchronous Annotation

    +
  • +
  • +

    Setting Timeouts

    +
  • +
  • +

    Implementing Fallback Logic

    +
  • +
  • +

    Isolating Resources for Fault Tolerance

    +
  • +
+
+
+
+
+

What is Fault Tolerance?

+
+
+

Fault tolerance is a system’s ability to continue working correctly even in case of unexpected failures. A fault-tolerant system should be able to detect, isolate, and recover from errors without human intervention. It is critical in applications based on modern microservices architectures where individual component failures are inevitable due to network issues, resource limitations, or transient errors.

+
+
+
+
+

Key Strategies for Enhancing Fault Tolerance

+
+
+

Some of the key strategies for enhancing the fault tolerance of a microservices-based application include:

+
+
+

Asynchronous Execution

+
+

Asynchronous execution allows operations to run in a separate thread. It means the caller does not have to wait for the operation to finish, making the application more responsive. For example, when a user searches for products in the product catalog service, the service can asynchronously fetch product recommendations from an external API while immediately returning the main search results to the user, ensuring a fast and responsive experience.

+
+
+

When applied individually or in combination, these strategies form the foundation of a fault-tolerant microservices architecture. The following sections delve deeper into their implementation and best practices.

+
+
+
+

Timeout

+
+

A timeout sets a time limit for operations, preventing indefinite waits and freeing up system resources for other tasks. For instance, a timeout in payment service ensures that the application can recover gracefully if the payment processing is taking too long to respond.

+
+
+
+

Retry

+
+

A retry allows the system to automatically retry failed operations, particularly useful for handling transient errors like temporary network glitches. You can customize the retry policy with parameters such as the delay between retries and maximum retries. Adding jitter prevents synchronized retries across services.

+
+
+

For example, a payment service can retry a failed payment authorization request with an external payment gateway to ensure successful transaction processing.

+
+
+
+

Bulkhead

+
+

A bulkhead isolates failures in one part of a system from other parts by segregating resources, such as thread pools, connection pools, or memory, among different microservices interactions.

+
+
+

For example, in an e-commerce application, the catalog service can implement bulkheads using separate thread pools or connection pools for different upstream dependencies, such as the product database and the pricing service. If the pricing service becomes slow or unresponsive, a bulkhead prevents it from consuming all the resources of the catalog service, ensuring that requests to fetch product details from the database continue to work unaffected.

+
+
+
+

Fallback

+
+

A fallback provides a default response if an operation fails. It ensures the system continues providing a meaningful response instead of completely failing. For example, if the database fails or becomes slow in the product catalog service, the system can fetch cached product data to continue serving user requests for product listings.

+
+
+
+

Circuit Breaker

+
+

A circuit breaker stops an application from making too many unsuccessful requests to another system. If the number of failures exceeds a threshold, the circuit breaker will open, causing all subsequent requests to fail immediately. After a configured delay, the circuit breaker will half-open and allow limited requests. If those requests succeed, the circuit breaker will close and let all requests go through.

+
+
+
+MicroProfile Fault Tolerance +
+
MicroProfile Fault Tolerance 1. MicroProfile Fault Tolerance
+
+
+

For example, a circuit breaker can be applied to calls to an external inventory service in the Product Catalog Microservice. If the inventory service starts failing or becomes unresponsive, the circuit breaker will open, preventing repeated requests and reducing load. After a configured delay, the circuit breaker will half-open to test the availability of the inventory service with a few requests. If those succeed, the circuit breaker will close, resuming normal operations.

+
+
+
+
+
+

Fault Tolerance API

+
+
+

The Fault Tolerance API equips developers with annotations to enhance the resilience of microservices against failures. It integrates seamlessly with the MicroProfile Config API, enabling the dynamic configuration of fault tolerance behaviors without modifying the application code. This section will explore using the Fault Tolerance API to build a robust, fault-tolerant microservice.

+
+
+

Adding Dependency for Fault Tolerance API

+
+

To use the Fault Tolerance API in your project, include the following dependency in your pom.xml file. Ensure you specify the version (e.g., 4.1.1) compatible with your MicroProfile runtime.

+
+
+
+
<dependency>
+  <groupId>org.eclipse.microprofile.fault-tolerance</groupId>
+  <artifactId>microprofile-fault-tolerance-api</artifactId>
+  <version>4.1.1</version>
+</dependency>
+
+
+
+

The Fault Tolerance API defines a contract for fault tolerance implementations.

+
+
+
+
+
+

MicroProfile Fault Tolerance Annotations

+
+
+

The MicroProfile Fault Tolerance annotations provide a declarative way to implement fault-tolerant behavior in Java methods, allowing developers to handle failures gracefully with minimal code changes.

+
+
+

List of Annotations

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Annotation

Description

@Asynchronous

Ensures that the annotated method executes in a separate thread, allowing non-blocking execution. This is useful for improving responsiveness and handling long-running tasks asynchronously.

@Retry

Specifies that the annotated method should automatically retry on failure. Parameters such as maxRetries, delay, maxDuration, and jitter control retry behavior. Configurations can be externalized using MicroProfile Config.

@Timeout

Specifies the maximum duration (in milliseconds) the method can execute before being aborted. If the timeout is exceeded, a FaultToleranceException is thrown.

@CircuitBreaker

Defines a circuit breaker mechanism to prevent repeated calls to a failing method. Includes parameters like failureRatio, delay, and requestVolumeThreshold.

@Fallback

Specifies alternative logic to execute when the primary method fails. This ensures meaningful responses and graceful degradation.

@Bulkhead

Limits the number of concurrent method executions to isolate system resources and prevent cascading failures.

+
+
+

Implementing Retry Policies and Configuration

+
+

Retries are a fundamental fault tolerance strategy for managing transient failures such as temporary network outages or intermittent service unavailability. The @Retry annotation in the MicroProfile Fault Tolerance API provides a simple and effective way to implement retry policies. By customizing parameters such as the number of retries, delay between attempts, and conditions for retries, you can ensure your application responds to failures gracefully and minimizes downtime.

+
+
+

Applying @Retry in PaymentService class

+
+

Below is an example of applying the @Retry annotation in a processPayment method within a PaymentService class of the MicroProfile e-commerce project:

+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.MediaType;
+
+public class PaymentService {
+
+    @Retry(
+        maxRetries = 3,
+        delay = 2000,
+        jitter = 500,
+        retryOn = PaymentProcessingException.class,
+        abortOn = CriticalPaymentException.class
+    )
+    public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException {
+        System.out.println("Processing payment for amount: " + paymentDetails.getAmount());
+
+        // Simulating a transient failure
+        if (Math.random() > 0.7) {
+            throw new PaymentProcessingException("Temporary payment processing failure");
+        }
+
+        return Response.ok("{\"status\":\"success\"}", MediaType.APPLICATION_JSON).build();
+    }
+}
+
+
+
+
+

Defining the PaymentDetails Class

+
+

To store the necessary payment information, the following PaymentDetails class is used. This class acts as a simple data container for payment-related details.

+
+
+
+
class PaymentDetails {
+    private double amount;
+
+    public double getAmount() {
+        return amount;
+    }
+
+    public void setAmount(double amount) {
+        this.amount = amount;
+    }
+}
+
+
+
+
+

Creating Custom Exception Classes for Handling Failures

+
+

The PaymentProcessingException class represents a recoverable error, which triggers retries when thrown.

+
+
+
+
package io.microprofile.tutorial.store.payment.exception;
+
+public class PaymentProcessingException extends Exception {
+   public PaymentProcessingException(String message) {
+       super(message);
+   }
+}
+
+
+
+

The CriticalPaymentException is considered a non-recoverable failure. If this exception occurs, the retry process is aborted.

+
+
+
+
package io.microprofile.tutorial.store.payment.exception;
+
+class CriticalPaymentException extends Exception {
+    public CriticalPaymentException(String message) {
+        super(message);
+    }
+}
+
+
+
+

In this example, the processPayment method attempts to process a payment. If a transient failure occurs (e.g., PaymentProcessingException), the method retries up to three times (maxRetries = 3), and there is a delay of 2000 milliseconds between retries (delay = 2000), with a random variation of up to 500 milliseconds added to the delay (jitter = 500) to avoid synchronized retries (e.g. thundering herd problem). +The retries are attempted only for the exception PaymentProcessingException (retryOn = PaymentProcessingException.class) and are aborted if a CriticalPaymentException is encountered (abortOn = CriticalPaymentException.class).

+
+
+

This approach helps maintain application resilience while preventing unnecessary retries that could worsen critical failures.

+
+
+
+

Understanding the @Retry Parameters

+
+

A retry policy specifies the conditions under which an operation should be retried. The key attributes of the @Retry annotation include:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Parameter

Description

maxRetries

Specifies the maximum number of retries.

delay

Sets the time (in milliseconds) to wait between retry attempts.

jitter

Adds a random variation (in milliseconds) to the delay to avoid synchronized retries.

retryOn

Defines the exception(s) that should trigger a retry. Defaults to all exceptions if not specified.

abortOn

Specifies the exception(s) that should not trigger a retry, overriding the default retry behavior.

maxDuration

Limits the total time (in milliseconds) that retries can be attempted.

+
+
+

Externalizing Configuration with MicroProfile Config

+
+

Retry policies can be externalized using the MicroProfile Config API. This allows you to modify the retry behavior without changing the application code. Here’s how to externalize the configuration:

+
+
+
    +
  • +

    Add the @Retry annotation with minimal attributes:

    +
  • +
+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.MediaType;
+
+public class PaymentService {
+
+    @Retry
+    public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException {
+        System.out.println("Processing payment for amount: " + paymentDetails.getAmount());
+
+        // Simulating a transient failure
+        if (Math.random() > 0.7) {
+            throw new PaymentProcessingException("Temporary payment processing failure");
+        }
+
+        return Response.ok("{\"status\":\"success\"}", MediaType.APPLICATION_JSON).build();
+    }
+}
+
+
+
+
    +
  • +

    Define the retry policy in a configuration file (e.g., microprofile-config.properties):

    +
  • +
+
+
+
+
io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/maxRetries=3
+io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/delay=2000
+io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/jitter=500
+
+
+
+

In this approach, you gain flexibility to adapt retry policies based on the environment, such as increasing retry attempts in production or reducing delays during testing.

+
+
+
+

Best Practices for Retry Policies

+
+
    +
  • +

    Limit Retries: Avoid setting maxRetries too high, as excessive retries can overwhelm the system or cause cascading failures.

    +
  • +
  • +

    Use Jitter: Always configure jitter to reduce the risk of synchronized retry attempts by multiple services.

    +
  • +
  • +

    Abort Non-Recoverable Errors: Use the abortOn parameter to exclude critical exceptions that retries cannot resolve.

    +
  • +
  • +

    Monitor Metrics: Integrate with MicroProfile Metrics to track retry patterns and adjust configurations dynamically based on real-world performance.

    +
  • +
  • +

    Combine Strategies: For robust error handling, use retries alongside other fault tolerance mechanisms, such as timeouts and circuit breakers.

    +
  • +
+
+
+
+
+

Avoiding and Managing Cascading Failures

+
+

In a distributed microservices architecture, cascading failures occur when the failure of one service propagates to others, potentially causing widespread system outages. Such failures often result from tightly coupled services, unbounded retries, or resource exhaustion.

+
+
+

Causes of Cascading Failures

+
+
    +
  • +

    Tight Coupling: Dependencies between services without sufficient isolation mechanisms.

    +
  • +
  • +

    Unbounded Retries: Excessive retries on failing services, overwhelming resources.

    +
  • +
  • +

    Resource Contention: Exhaustion of critical resources such as thread pools, memory, or database connections.

    +
  • +
  • +

    Lack of Fail-Safe Mechanisms: Missing circuit breakers, bulkheads, or fallback logic.

    +
  • +
+
+
+
+

Strategies to Prevent Cascading Failures

+
+
    +
  • +

    Use circuit breakers to isolate failing services.

    +
  • +
  • +

    Apply bulkheads to limit the scope of failures and resource usage.

    +
  • +
  • +

    Set timeouts to prevent long-running operations from blocking resources.

    +
  • +
  • +

    Design retries with care to avoid overwhelming the system.

    +
  • +
+
+
+
+
+

Configuring Circuit Breaker

+
+

A circuit breaker is a critical fault tolerance mechanism that protects a system from repeated failures of a dependent service. It stops repeated calls to a failing service, allowing it to recover.

+
+
+

Circuit Breaker Parameters

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

Parameter

Description

failureRatio

Specifies the proportion of failed requests required to open the circuit breaker.

requestVolumeThreshold

The minimum number of requests made in a rolling time window before the failure ratio is evaluated.

delay

The time (in milliseconds) the circuit breaker remains open before transitioning to the "half-open" state.

successThreshold

The number of consecutive successful test requests required in the "half-open" state to close the circuit breaker.

failOn

Specifies the exception(s) considered failures contributing to the failure ratio.

+
+

Below is an example of configuring a circuit breaker for a service method using the @CircuitBreaker annotation:

+
+
+
+
@CircuitBreaker(
+    requestVolumeThreshold = 10,
+    failureRatio = 0.5,
+    delay = 5000,
+    successThreshold = 2,
+    failOn = RuntimeException.class
+)
+public String getProduct(Long id) {
+    // Logic to call the product details service
+    if (Math.random() > 0.7) {
+        throw new RuntimeException("Simulated service failure");
+    }
+    return productRepository.findProductById(id);
+}
+
+
+
+

In the above code, the circuit breaker opens if 50% of requests fail (failureRatio = 0.5) after at least 10 requests (requestVolumeThreshold = 10). It remains open for 5 seconds (delay = 5000) and transitions to the "half-open" state to test recovery. Two consecutive successful requests (successThreshold = 2) in the "half-open" state close the circuit breaker.

+
+
+
+

Externalizing Circuit Breaker Configuration

+
+

Using MicroProfile Config, you can externalize circuit breaker parameters to make them adjustable without code changes as below:

+
+
+
    +
  • +

    Update the @CircuitBreaker annotation:

    +
  • +
+
+
+
+
@CircuitBreaker (failOn = RuntimeException.class)
+public String getProduct(Long id) {
+    // Logic to call the product details service
+    if (Math.random() > 0.7) {
+        throw new RuntimeException("Simulated service failure");
+    }
+    return productRepository.findProductById(id);
+}
+
+
+
+
    +
  • +

    Define the configuration in microprofile-config.properties:

    +
  • +
+
+
+
+
io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/requestVolumeThreshold=10
+io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/failureRatio=0.5
+io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/delay=5000
+io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/successThreshold=2
+
+
+
+
+

Best Practices for Circuit Breaker

+
+
    +
  • +

    Set Realistic Failure Ratios and Thresholds: Tailor parameters to your services' expected load and failure behavior.

    +
  • +
  • +

    Monitor Metrics: Use MicroProfile Metrics to monitor circuit breaker state transitions.

    +
  • +
  • +

    Combine with Other Strategies: Use circuit breakers alongside retries and timeouts for a robust fault tolerance setup.

    +
  • +
+
+
+
+
+

Using @Asynchronous Annotation

+
+

The @Asynchronous annotation in MicroProfile Fault Tolerance is used to enable asynchronous execution of methods. It allows operations to run in a separate thread, freeing up the main thread for other tasks. This approach enhances the application’s responsiveness and scalability, particularly in high-concurrency or latency-sensitive scenarios.

+
+
+

Why Use @Asynchronous?

+
+
    +
  1. +

    Improved Responsiveness: The caller does not need to wait for the method execution to complete, allowing the application to remain interactive.

    +
  2. +
  3. +

    Non-Blocking Execution: Long-running operations are offloaded to a separate thread, preventing bottlenecks.

    +
  4. +
  5. +

    Scalability: By decoupling method execution from the calling thread, you can handle higher loads without increasing thread contention.

    +
  6. +
+
+
+
+

Implementation

+
+

Below is an example of using the @Asynchronous annotation with MicroProfile Fault Tolerance:

+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import org.eclipse.microprofile.faulttolerance.Bulkhead;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+@ApplicationScoped
+public class PaymentService {
+
+    private static final int SIMULATED_DELAY_MS = 2000;
+
+    /**
+     * Processes payments asynchronously
+     *
+     * @return A CompletionStage with the result of the operation.
+     */
+    @Asynchronous
+    public CompletionStage<String> processPayment() {
+        simulateDelay();
+        return CompletableFuture.completedFuture("Payment processed asynchronously.");
+    }
+
+    /**
+     * Simulates a delay in processing
+     */
+    private void simulateDelay() {
+        try {
+            Thread.sleep(SIMULATED_DELAY_MS); // Simulating delay
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Error during simulated delay", e);
+        }
+    }
+}
+
+
+
+
+

Externalizing Timeout Configuration

+
+

Timeout values can be externalized using the MicroProfile Config API, allowing flexibility to adjust values without modifying code. Here’s how: +* Define the @Timeout annotation without specifying the value:

+
+
+
+
@Timeout
+public String fetchData() {
+    // Logic
+}
+
+
+
+
    +
  • +

    Configure the timeout in microprofile-config.properties:

    +
  • +
+
+
+
+
io.microprofile.tutorial.store.payment.service.ProductService/fetchData/Timeout/value=1500
+
+
+
+
+

Best Practices for Using @Asynchronous

+
+
    +
  • +

    Use CompletableStage or Future: Return types like CompletableStage allow asynchronous methods to integrate seamlessly with other asynchronous workflows.

    +
  • +
+
+
+
+

Asynchronous Execution in Fault Tolerance Strategies

+
+

When used with other fault tolerance strategies, @Asynchronous provides a powerful mechanism to handle faults without impacting the system’s responsiveness:

+
+
+
    +
  1. +

    Asynchronous with Bulkhead:

    +
    +
      +
    • +

      Isolates resources while maintaining non-blocking execution.

      +
    • +
    • +

      Handles concurrent requests efficiently using thread pools.

      +
    • +
    +
    +
  2. +
  3. +

    Asynchronous with Circuit Breaker:

    +
    +
      +
    • +

      Prevents system overload during failures by breaking the circuit for failing asynchronous methods.

      +
    • +
    • +

      The circuit breaker’s delay allows recovery while new threads are available for other tasks.

      +
    • +
    +
    +
  4. +
+
+
+
+
+

Setting Timeouts

+
+

Timeouts are an essential fault tolerance strategy to prevent long-running operations from consuming resources indefinitely. Slow or unresponsive services can degrade overall system performance and reliability in a microservices architecture. The @Timeout annotation provided by MicroProfile Fault Tolerance allows you to define a maximum duration for a method to complete, ensuring that system resources remain available for other tasks.

+
+
+

Why Use Timeouts?

+
+

In distributed systems, slow responses from downstream services can cascade through the system, leading to resource contention and degraded performance. Timeouts allow you to: +- Abort operations that exceed acceptable time limits. +- Free system resources for other operations. +- Trigger alternative strategies, such as fallbacks, to maintain functionality.

+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import io.microprofile.tutorial.store.payment.entity.PaymentDetails;
+import io.microprofile.tutorial.store.payment.exception.PaymentProcessingException;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import org.eclipse.microprofile.faulttolerance.Timeout;
+
+@ApplicationScoped
+public class PaymentService {
+
+    private static final int TIMEOUT_MS = 1000;
+    private static final double FAILURE_THRESHOLD = 0.7;
+
+    @Inject
+    private Logger logger;
+
+    /**
+     * Processes payments asynchronously with a timeout.
+     *
+     * @param paymentDetails the payment details
+     * @return a CompletionStage with the result of the operation
+     */
+    @Asynchronous
+    @Timeout(TIMEOUT_MS)
+    public CompletionStage<String> processPayment(PaymentDetails paymentDetails) {
+        return CompletableFuture.supplyAsync(() -> {
+            simulateDelay();
+            logger.info("Processing payment for amount: " + paymentDetails.getAmount());
+
+            if (Math.random() > FAILURE_THRESHOLD) {
+                throw new PaymentProcessingException("Temporary payment processing failure");
+            }
+
+            return "{\"status\":\"success\", \"message\":\"Payment processed successfully.\"}";
+        }).exceptionally(ex -> {
+            logger.warning("Payment processing failed: " + ex.getMessage());
+            return "{\"status\":\"failure\", \"message\":\"Payment failed due to a temporary issue.\"}";
+        });
+    }
+
+    /**
+     * Simulates a delay in processing.
+     */
+    private void simulateDelay() {
+        try {
+            Thread.sleep(2000); // Simulating delay
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            logger.severe("Error during simulated delay: " + e.getMessage());
+            throw new RuntimeException("Error during simulated delay", e);
+        }
+    }
+}
+
+
+
+

In this example, the @Timeout(1000) annotation specifies that the processPayment method must complete within 1000 milliseconds (1 second). If the execution exceeds this time, a TimeoutException will be thrown, and the process will terminate. @Asynchronous ensures non-blocking execution by making the method run in a separate thread. To explore the benefits of asynchronous programming with MicroProfile Fault Tolerance, the following resources provide valuable insights and real-world examples:

+
+ +
+

These articles explain how asynchronous execution enhances system responsiveness, reduces blocking, and ensures better resource utilization in MicroProfile applications.

+
+
+
+

Best Practices for Timeouts

+
+
    +
  • +

    Align Timeouts with SLAs: Ensure timeout values align with service-level agreements and user expectations.

    +
  • +
  • +

    Monitor Performance: Use MicroProfile Metrics to monitor execution times and identify operations requiring optimized timeout values.

    +
  • +
  • +

    Combine with Fallbacks: Always pair timeouts with fallback logic to provide a reliable response in case of delays.

    +
  • +
  • +

    Avoid Overly Short Timeouts: Overly aggressive timeout settings may cause unnecessary failures, particularly in high-latency environments.

    +
  • +
  • +

    Combine Timeout with Asynchronous: Use timeout together with asynchronous to improve responsiveness and prevent blocking the calling thread. This approach ensures better resource utilization and system scalability during long-running operations.

    +
  • +
+
+
+
+
+

Implementing Fallbacks

+
+

Fallbacks provide a default response when an operation fails. They ensure the system continues to function, even if the primary operation cannot complete successfully. The @Fallback annotation in MicroProfile Fault Tolerance allows developers to define fallback logic for a method, ensuring graceful degradation.

+
+
+

Why Use Fallbacks?

+
+

Fallbacks help to: +- Maintain system availability during failures. +- Provide a meaningful response to users instead of complete failure. +- Improve user experience by minimizing disruptions.

+
+
+
+
import org.eclipse.microprofile.faulttolerance.Fallback;
+import jakarta.ws.rs.core.Response;
+
+public class PaymentService {
+
+    @Fallback(fallbackMethod = "fallbackProcessPayment")
+    public Response processPayment(PaymentDetails paymentDetails) {
+        // Simulate a failure
+        throw new RuntimeException("Service Unavailable");
+    }
+
+    public Response fallbackProcessPayment(PaymentDetails paymentDetails) {
+        return Response.ok("{\"status\":\"failed\", \"message\":\"Payment service is currently unavailable.\"}").build();
+    }
+}
+
+
+
+

In this example: +- The @Fallback annotation specifies that if the processPayment method fails, the fallbackProcessPayment method will be executed. +- The fallback method provides a meaningful response, ensuring the user is informed of the service unavailability.

+
+
+
+

Using Fallback Handlers

+
+

A fallback handler class can implement the FallbackHandler<T> interface, allowing for reusable fallback logic across multiple methods.

+
+
+
+
import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.FallbackHandler;
+import org.eclipse.microprofile.faulttolerance.ExecutionContext;
+
+
+public class ProductService {
+
+    @Fallback(FallbackHandlerImpl.class)
+    public Product getProduct(Long id) {
+        // Logic to call the product details service
+        if (Math.random() > 0.7) {
+            throw new RuntimeException("Simulated service failure");
+        }
+
+        return productRepository.findProductById(id);
+    }
+}
+
+public class FallbackHandlerImpl implements FallbackHandler<String> {
+    @Override
+    public String handle(ExecutionContext context) {
+        return "Fallback response for product details.";
+    }
+}
+
+
+
+
+

Combining Fallbacks with Other Fault Tolerance Strategies

+
+

Fallback logic can be combined with other fault tolerance mechanisms to create a robust strategy: +- Timeout with Fallback: Ensure operations terminate within a specific time and provide a fallback if they fail.

+
+
+

Example:

+
+
+
+
import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.Timeout;
+
+import jakarta.enterprise.context.RequestScoped;
+
+import io.microprofile.tutorial.store.product.cache.ProductCache;
+import io.microprofile.tutorial.store.product.entity.Product;
+
+@RequestScoped
+public class ProductService {
+
+    @Inject
+    private ProductRepository productRepository; // Access to the database
+
+    @Inject
+    private ProductCache productCache; // Cache mechanism
+
+    /**
+     * Retrieves a list of products. If the operation takes longer than 2 seconds,
+     * fallback to cached data.
+     */
+    @Timeout(2000) // Set timeout to 2 seconds
+    @Fallback(fallbackMethod = "getProductsFromCache") // Fallback method
+    public List<Product> getProducts() {
+        if (Math.random() > 0.7) {
+            throw new RuntimeException("Simulated service failure");
+        }
+        // database call
+        return productRepository.findAllProducts();
+    }
+
+    /**
+     * Fallback method to retrieve products from the cache.
+     */
+    public List<Product> getProductsFromCache() {
+        System.out.println("Fetching products from cache...");
+        return productCache.getAll().stream()
+                .map(obj -> (Product) obj)
+                .collect(Collectors.toList());
+    }
+}
+
+
+
+

This example demonstrates the use of MicroProfile Fault Tolerance annotations @Timeout and @Fallback to enhance the resilience of the ProductService. When getProducts() method is invoked, the application tries to retrieve product data from the database using productRepository.findAllProducts(). The @Timeout(2000) annotation ensures that this operation completes within 2 seconds. If the query executes successfully within this time, the method returns the product list as expected. However, if the execution time exceeds the timeout limit, a TimeoutException is triggered. Additionally, if an exception occurs within the time limit, the method also fails. To handle such failures gracefully, the @Fallback annotation specifies getProductsFromCache() as an alternative method. When a timeout or exception occurs, the fallback method is invoked, fetching product data from the cache instead of the database. This approach guarantees service availability and ensures a seamless user experience, even in scenarios where the database is slow or temporarily unavailable. For improved scalability and performance, @Asynchronous can be combined with @Timeout and @Fallback. This allows the method to execute in a non-blocking manner, freeing up system resources and enabling parallel processing of multiple requests. By utilizing asynchronous execution, the application can handle high loads efficiently while maintaining fault tolerance.

+
+
+
+

Externalizing @Timeout Configuration using MicroProfile Config

+
+

To externalize the @Timeout configuration using MicroProfile Config, you can replace the hardcoded timeout value with a configurable property. This allows us to modify the timeout dynamically without changing the source code.

+
+
+
    +
  • +

    Define a Configurable Property: Use @ConfigProperty to inject the timeout value.

    +
  • +
+
+
+
+
// ...
+@RequestScoped
+public class ProductService {
+
+    @Inject
+    private ProductRepository productRepository; // Access to the database
+
+    @Inject
+    private ProductCache productCache; // Cache mechanism
+
+    // Inject the timeout value from MicroProfile Config
+    @Inject
+    @ConfigProperty(name = "product.service.timeout", defaultValue = "2000")
+    private long timeoutValue;
+
+    // ...
+
+
+
+
    +
  • +

    Use the Configured Value in @Timeout Annotation: Define a getter method and using it in the annotation.

    +
  • +
+
+
+
+
    ...
+    /**
+     * Provide the timeout value dynamically using a method reference.
+     */
+    @Timeout(value = getTimeout()) // Use method reference to fetch dynamic value
+    public long getTimeout() {
+        return timeoutValue;
+    }
+
+
+
+
    +
  • +

    Define the Configuration Property: Configure the timeout in microprofile-config.properties:

    +
  • +
+
+
+
+
io.microprofile.tutorial.store.product.service.ProductService.timeout=3000
+
+
+
+

This sets the timeout to 3000 milliseconds (3 seconds) instead of the default 2000 making your application more configurable and adaptable without code changes.

+
+
+
+

Best Practices for Fallbacks

+
+
    +
  • +

    Keep Fallbacks Lightweight: Ensure fallback logic is simple and reliable, avoiding dependencies on other potentially failing services.

    +
  • +
  • +

    Provide Meaningful Responses: The fallback response should maintain a reasonable user experience, even if it cannot replicate full functionality.

    +
  • +
  • +

    Monitor Fallback Usage: Use metrics to track the frequency of fallback execution, which can indicate service health and the need for improvements.

    +
  • +
  • +

    Plan for Degraded Functionality: Ensure the fallback behavior aligns with business priorities and provides the most critical features.

    +
  • +
+
+
+
+
+

Combining Fault Tolerance Strategies

+
+

Combining fault tolerance strategies, such as @Timeout, @Fallback, @CircuitBreaker, and @Retry, ensures resilience and efficient resource usage. Externalize configurations with MicroProfile Config for flexibility across environments.

+
+
+
+

Isolating Resources for Fault Tolerance

+
+

Resource isolation is a key principle in building resilient microservices. By isolating resources, you prevent failures in one part of the system from spreading and affecting others. MicroProfile Fault Tolerance provides features like bulkheads to achieve resource isolation and ensure critical components remain functional, even when others fail.

+
+
+

Why Resource Isolation Matters

+
+

In a distributed system, shared resources like thread pools, database connections, and network bandwidth can quickly become bottlenecks if not adequately managed. Resource isolation ensures: +- Failures in one service do not deplete resources for other services. +- Critical operations remain functional even under load or failure conditions. +- Better predictability and control over system behavior.

+
+
+
+

Using Bulkheads to Isolate Resources

+
+

Bulkheads are a common pattern for isolating resources by dividing a system into separate pools or partitions. This ensures that a failure in one area does not impact others. The MicroProfile Fault Tolerance standard provides the @Bulkhead annotation to implement this pattern.

+
+
+
+

Bulkhead Types

+
+

MicroProfile supports two types of bulkheads:

+
+
+
    +
  • +

    Semaphore-Style Bulkhead: Limits the number of concurrent requests.

    +
  • +
  • +

    Thread Pool-Style Bulkhead: Runs a maximum number of requests on a thread pool to isolate operations.

    +
  • +
+
+
+
Semaphore-Style Bulkhead
+
+

The semaphore-style bulkhead pattern limits the number of concurrent requests that can be processed by a service or method at any given time. Any additional requests are immediately rejected when the specified concurrency limit is reached. This approach prevents resource contention and protects the system from being overwhelmed during high traffic or failure scenarios.

+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import org.eclipse.microprofile.faulttolerance.Bulkhead;
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.logging.Logger;
+
+@ApplicationScoped
+public class PaymentService {
+
+    @Inject
+    private Logger logger;
+
+    @Inject
+    @ConfigProperty(name = "payment.simulatedDelay", defaultValue = "1000")
+    private int simulatedDelay;
+
+    @Inject
+    @ConfigProperty(name = "payment.bulkhead.value", defaultValue = "5")
+    private int bulkheadValue;
+
+    /**
+     * Processes payment transactions with limited concurrency to prevent
+     * system overload and ensure stability during high traffic.
+     *
+     * The @Bulkhead annotation ensures that only a limited number of
+     * concurrent requests can access this method.
+     * The @Asynchronous annotation enables the use of the thread pool
+     * style bulkhead for non-blocking execution.
+     *
+     * @return A success message indicating the processing status.
+     */
+    @Asynchronous
+    @Bulkhead(value = bulkheadValue)
+    public CompletionStage<String> processPayment() {
+        logger.info("Starting payment processing...");
+        simulateDelay();
+        logger.info("Payment processing completed.");
+        return CompletableFuture.completedFuture("Payment processed asynchronously.");
+    }
+
+    /**
+     * Simulates a delay in processing.
+     */
+    private void simulateDelay() {
+        try {
+            Thread.sleep(simulatedDelay); // Simulating delay
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            logger.severe("Error during simulated delay: " + e.getMessage());
+            throw new RuntimeException("Error during simulated delay", e);
+        }
+    }
+}
+
+
+
+

In this example: +- The method allows up to 5 concurrent invocations (value = 5). +- Any additional requests are rejected to prevent overload, ensuring system stability.

+
+
+
+
Thread Pool-Style Bulkhead
+
+

The thread-pool-style bulkhead pattern leverages a thread pool to achieve resource isolation. Incoming requests are placed into a queue when the maximum allowed number of threads are in use. Queued requests are executed as threads become available. This design helps manage resource contention effectively.

+
+
+
+
package io.microprofile.tutorial.store.payment.service;
+
+import org.eclipse.microprofile.faulttolerance.Bulkhead;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+@ApplicationScoped
+public class PaymentService {
+
+    private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
+
+    /**
+     * Processes payment transactions with limited concurrency using a thread pool
+     * to prevent system overload and ensure stability during high traffic.
+     *
+     * The @Bulkhead annotation ensures that only a limited number of concurrent
+     * requests (5 in this case) can access this method, and the @Asynchronous
+     * annotation allows the use of the thread pool style bulkhead.
+     */
+    @Bulkhead(value = 5, waitingTaskQueue = 10)
+    @Asynchronous
+    public CompletionStage<Void> processPayment() {
+        return CompletableFuture.runAsync(() -> {
+            simulateDelay();
+            System.out.println("Payment processed with limited concurrency.");
+        }).thenRun(() -> logger.info("Payment processed with limited concurrency."));
+    }
+
+    private void simulateDelay() {
+        try {
+            Thread.sleep(1000); // Simulating a delay
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Error during payment processing simulation", e);
+        }
+    }
+}
+
+
+
+

In this example, The method uses up to 5 concurrent threads (value = 5) from a thread pool and a queue of up to 10 tasks (waitingTaskQueue = 10).This configuration prevents failures in one operation from depleting shared resources.

+
+
+
+
+

Externalizing Bulkhead Configuration

+
+

Bulkhead resource limits can be externalized using MicroProfile Config to allow runtime adjustments. For example:

+
+
+

Annotate the method without specific values:

+
+
+
+
    @Asynchronous
+    @Bulkhead
+    public CompletionStage<String> processPayment() {
+    logger.info("Starting payment processing...");
+    simulatePaymentProcessing();
+    logger.info("Payment processing completed.");
+    return CompletableFuture.completedFuture("Payment processed successfully with an isolated thread pool.");
+    }
+
+
+
+

Define bulkhead parameters in microprofile-config.properties:

+
+
+
+
com.example.Service/dynamicBulkheadOperation/Bulkhead/value=5
+com.example.Service/dynamicBulkheadOperation/Bulkhead/waitingTaskQueue=10
+
+
+
+
+

Best Practices for Resource Isolation

+
+
    +
  • +

    Isolate Critical Resources: Use bulkheads for high-priority operations, such as authentication, to ensure they are not impacted by failures elsewhere.

    +
  • +
  • +

    Monitor Usage: Track bulkhead metrics using MicroProfile Metrics to identify bottlenecks and adjust limits.

    +
  • +
  • +

    Plan for Scaling: Test bulkhead configurations under various load conditions to ensure scalability.

    +
  • +
  • +

    Combine with Graceful Degradation: Pair bulkheads with fallbacks to handle rejected requests gracefully.

    +
  • +
+
+
+

By effectively isolating resources, you can ensure that your microservices remain reliable and resilient, even in the face of unexpected failures or high demand. This approach not only protects critical operations but also improves overall system stability.

+
+
+
+
+
+
+

Summary

+
+
+

This chapter explored the MicroProfile Fault Tolerance API and essential fault tolerance strategies:

+
+
+
    +
  • +

    Retries: Automatically reattempt failed operations for transient errors.

    +
  • +
  • +

    Timeouts: Define maximum execution times for operations to avoid resource blocking.

    +
  • +
  • +

    Circuit Breakers: Prevent repeated calls to failing services and allow graceful recovery.

    +
  • +
  • +

    Bulkheads: Limit concurrent operations and isolate resource usage.

    +
  • +
  • +

    Fallbacks: Provide meaningful responses during failures.

    +
  • +
+
+
+

By leveraging these strategies and combining them effectively, you can design resilient microservices that gracefully handle failures, minimize disruptions, and ensure a seamless user experience.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter09/index.html b/build/site/microprofile-tutorial/6.1/chapter09/index.html new file mode 100644 index 00000000..df167b39 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter09/index.html @@ -0,0 +1,1162 @@ + + + + + +MicroProfile Telemetry :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Telemetry

+
+
+
+

Microservices-based applications have better scalability, flexibility, and resilience, but they suffer from additional challenges regarding availability and performance monitoring. This makes observability critical to ensure these distributed systems operate reliably.

+
+
+

MicroProfile Telemetry specification provides a set of vendor-neutral APIs for instrumenting, collecting, and exporting telemetry data such as traces, metrics, and logs. It is built on the foundation of OpenTelemetry from the Cloud Native Computing Foundation (CNCF) project, an open-source observability framework.

+
+
+

In this chapter, we will explore the fundamentals of MicroProfile Telemetry, covering topics such as tracing concepts, instrumenting Telemetry, setting up tracing providers, context propagation and correlation, analyzing traces, security considerations for tracing, and more. By the end of this chapter, you will learn how to effectively leverage distributed tracing for debugging, performance monitoring, and system optimization.

+
+
+
+
+

Topics to be covered

+
+
+
    +
  • +

    Introduction to MicroProfile Telemetry

    +
  • +
  • +

    Tracing Concepts

    +
    +
      +
    • +

      Spans

      +
    • +
    • +

      Traces

      +
    • +
    • +

      Context Propagation

      +
    • +
    • +

      Correlation

      +
    • +
    +
    +
  • +
  • +

    Instrumenting OpenTelemetry

    +
  • +
  • +

    Tools for Trace Analysis

    +
  • +
  • +

    Exporting the Traces

    +
  • +
  • +

    Types of Telemetry

    +
  • +
  • +

    Agent Instrumentation

    +
  • +
  • +

    Analyzing Traces

    +
  • +
  • +

    Security Considerations for Tracing

    +
  • +
+
+
+
+
+

Introduction to MicroProfile Telemetry

+
+
+

MicroProfile Telemetry addresses the operational challenges inherent in modern microservices architectures. Without proper observability, debugging, performance monitoring, and ensuring system reliability become complex and time-consuming.

+
+
+

Some of the key challenges in microservices-based applications include:

+
+
+
    +
  • +

    Complexity due to Distributed Architecture: Microservices are often deployed across multiple nodes, containers, or cloud environments, making it challenging to track requests as they move through the system. This lack of visibility increases debugging complexity, making it harder to identify bottlenecks and analyze system behavior.

    +
  • +
  • +

    Polyglot Architecture: Microservices are developed using multiple programming languages (e.g., Java, Python, and Go) and frameworks, resulting in inconsistent telemetry data and a lack of standardization in observability. This fragmentation makes correlating logs, traces, and metrics across services difficult.

    +
  • +
  • +

    Latency: Communication between Microservices involves latency, and all of this adds up as requests traverse several services. This makes it difficult to identify the root causes of issues. +Ensuring High Availability: Failures in one microservice can affect the entire system, impacting multiple dependent microservices. This can lead to downtime or degraded performance, resulting in lost revenue and diminished user trust.

    +
  • +
+
+
+

To address these challenges, MicroProfile Telemetry specification provides a standardized set of APIs for capturing telemetry data, including trace information and context propagation, to improve observability in distributed systems. By enabling seamless tracing, developers can analyze system behavior, troubleshoot service interactions, and ensure application reliability.

+
+
+

MicroProfile Telemetry is vendor-neutral. It allows developers to switch between different OpenTelemetry implementations without modifying their application code. This flexibility ensures that MicroProfile applications can easily integrate with various observability platforms, making it easier to adopt, scale, and maintain Telemetry in modern cloud-native environments.

+
+
+
+
+

Tracing Concepts

+
+
+

Tracing is critical for observability. It allows developers to inspect the flow of requests as they traverse through distributed systems. Tracing provides visibility into the interactions and dependencies within a system by breaking down a request into multiple spans, and connecting them into traces with context propagated across services.

+
+
+

Spans

+
+

A span is the basic unit of work in tracing. It represents a single operation or task a service performs, such as an HTTP request, a database query, or a computation. Each span contains metadata, including:

+
+
+
    +
  • +

    Operation Name: Describes the activity (e.g., HTTP GET /products).

    +
  • +
  • +

    Start Time and Duration: Captures when the operation started and how long it took.

    +
  • +
  • +

    Attributes: Key-value pairs providing context (e.g., user IDs, resource names, HTTP status codes).

    +
  • +
  • +

    Parent Span ID: Indicates the parent span, forming a relationship within a trace.

    +
  • +
+
+
+

Spans may also include additional data like logs and events, which help provide a detailed view of the operation’s lifecycle. Spans are connected to form a trace, which helps identify bottlenecks and performance issues.

+
+
+
+

Traces

+
+

A trace is a collection of related spans representing the end-to-end execution of a request or transaction. It provides a holistic view of how a single request flows through the system, including service interactions. Traces often form a tree structure, where the root span represents the entry point (e.g., a user request), and child spans represent subsequent operations.

+
+
+

For example:

+
+
+
+
API Gateway (Root Span) +
+│
+├── Order Service (Child Span) +
+│   │
+│   ├── Database Query (Another Child Span) +
+│   │   ├── Fetch Order Details +
+│   │   ├── Process Order Data +
+│   │   └── Return Data to Order Service +
+│   │
+│   └── Return Response to API Gateway +
+│
+└── API Gateway Sends Final Response to User
+
+
+
+
+

Context Propagation

+
+

Context propagation refers to the mechanism of carrying trace-related metadata, such as trace IDs and span IDs, across service and thread boundaries. This ensures that all spans created during a request can be linked together to form a complete trace.

+
+
+
+

Correlation

+
+

Context propagation is vital for connecting distributed spans and understanding their relationship ensuring trace metadata remains correlated as it travels with requests across service boundaries. +Correlation is the process of associating related spans and traces across multiple services and threads to form a cohesive view of a transaction. Correlation enables developers to:

+
+
+
    +
  • +

    Identify the source of bottlenecks or errors in distributed systems.

    +
  • +
  • +

    Understand the dependencies and interactions between services.

    +
  • +
+
+
+

When viewing logs, the traceId and spanId allow you to link specific log entries to the corresponding spans in your tracing system.

+
+
+
    +
  • +

    Trace ID: A unique identifier shared across all spans in a single trace.

    +
  • +
  • +

    Span ID: A unique identifier for a single span. It is linked to a parent span, forming a hierarchy.

    +
  • +
+
+
+

Together, these concepts form the foundation of distributed tracing, enabling developers to monitor, analyze, and optimize the performance of their microservices effectively.

+
+
+
+
+
+

Instrumenting Telemetry

+
+
+

MicroProfile Telemetry simplifies instrumentation by integrating OpenTelemetry for distributed tracing. The following steps outline how to instrument telemetry in a MicroProfile E-Commerce application.

+
+
+

Step 1: Add the MicroProfile Telemetry Dependency

+
+

To enable tracing and exporting of telemetry data, include the MicroProfile Telemetry API dependency in your pom.xml file.

+
+
+
+
<!-- Adding MicroProfile Telemetry dependency -->
+<dependency>
+   <groupId>org.eclipse.microprofile.telemetry</groupId>
+   <artifactId>microprofile-telemetry-api</artifactId>
+   <version>1.1</version>
+   <scope>provided</scope>
+</dependency>
+
+
+
+
+

Step 2: Create a Tracer

+
+

MicroProfile automatically traces requests, but you can manually instrument your code using OpenTelementry APIs.

+
+
+

A Tracer is a core component of OpenTelemetry, responsible for creating spans and managing trace data within the application. To use it, inject a Tracer instance into your MicroProfile service:

+
+
+
+
import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.api.trace.Span;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class PaymentService {
+
+    @Inject
+    Tracer tracer;
+
+    public void processPayment(String orderId, double amount) {
+        // Create a custom span for tracing the payment process
+        Span span = tracer.spanBuilder("payment.process").startSpan();
+
+        try {
+            span.setAttribute("order.id", orderId);
+            span.setAttribute("payment.amount", amount);
+            span.setAttribute("payment.status", "IN_PROGRESS");
+
+            // Business logic for processing the payment
+            executePayment(orderId, amount);
+
+            span.setAttribute("payment.status", "SUCCESS");
+        } catch (Exception e) {
+            span.setAttribute("payment.status", "FAILED");
+            span.recordException(e);
+        } finally {
+            span.end();
+        }
+    }
+
+    private void executePayment(String orderId, double amount) {
+        System.out.println("Processing payment for Order ID: " + orderId + ", Amount: " + amount);
+    }
+}
+
+
+
+

The implementation injects a Tracer, which enables manual span creation and precise trace management within the application. By creating a custom span (payment.process), it captures detailed telemetry data related to the payment process. Additionally, custom attributes such as order.id, payment.amount, and payment.status are attached to the span, providing valuable metadata for trace analysis. The implementation also includes exception handling, ensuring that any failures encountered during payment processing are properly recorded in the trace. Finally, the span is explicitly ended, marking the completion of tracing for this method.

+
+
+

This setup ensures that each payment transaction is fully traceable, allowing developers to monitor execution flow, debug issues, and optimize application performance effectively.

+
+
+
+

Step 3: Create a Span

+
+

Use the Tracer to create a span that represents a specific operation or activity in your application:

+
+
+
+
Span span = tracer.spanBuilder("my-span").startSpan();
+
+
+
+

The method spanBuilder("my-span") creates a new named span, which represents a specific operation within the application’s execution flow. This helps in tracing and monitoring the operation as part of a distributed system. Calling startSpan() marks the beginning of the span lifecycle, ensuring that the span is actively recorded until it is explicitly ended. This allows telemetry data to be captured for performance analysis, debugging, and observability.

+
+
+
+

Step 4: Add Attributes to the Span

+
+

Attributes enhance trace context by attaching key-value pairs to a span, providing additional metadata that helps filter and analyze traces in observability tools. This helps in contextualizing the trace data:

+
+
+
+
span.setAttribute("http.method", "GET");
+span.setAttribute("http.url", "/products/12345");
+span.setAttribute("user.id", "98765");
+
+
+
+

The above statements allow the tracing system to capture essential details about an HTTP request.

+
+
+
+

Step 5: End the Span

+
+

When the operation completes, end the span to capture the telemetry data:

+
+
+
+
Span span = tracer.spanBuilder("payment.process").startSpan();
+
+try {
+    // Business logic execution
+} catch (Exception e) {
+    span.recordException(e);
+    span.setAttribute("error", true);
+} finally {
+    span.end();
+}
+
+
+
+
+
+
+

Tools for Trace Analysis

+
+
+

The following tools are commonly used for trace collection, visualization, and analysis in MicroProfile applications:

+
+
+

OpenTelemetry Collector

+
+

The OpenTelemetry Collector is an open-source telemetry processing system that acts as an intermediary between instrumented applications and observability backends such as Jaeger, Zipkin, and Prometheus. It is designed to receive, process, and export tracing data, making it a powerful tool for managing distributed traces in MicroProfile applications.

+
+
+

It is vendor-agnostic, which allows for seamless integration with multiple tracing backends without requiring any changes to application instrumentation. It supports multiple data formats, enabling the ingestion of traces through several protocols, ensuring compatibility across different telemetry sources. Additionally, it offers processing pipelines that let developers filter, batch, and transform trace data before exporting it, optimizing observability workflows.

+
+
+

Designed for scalability, the OpenTelemetry Collector can be deployed as a standalone instance or distributed across multiple nodes, making it suitable for both small-scale applications and large enterprise-grade distributed systems.

+
+
+
+

Jaeger

+
+

Jaeger is an open-source distributed tracing system developed by Uber, widely used for monitoring microservices and visualizing request flows in cloud-native applications. It provides a powerful visualization interface that enables developers to inspect traces, analyze dependencies between services, and examine execution timelines, making it an essential tool for debugging performance bottlenecks.

+
+
+

One of Jaeger’s key capabilities is service dependency analysis, which helps identify how microservices interact, providing insights into latency, failures, and request propagation. It also supports adaptive sampling strategies, allowing developers to control the volume of traces collected to optimize performance without overwhelming storage and processing resources. Additionally, Jaeger offers built-in storage options, allowing trace data to be stored in Elasticsearch, Cassandra, or Kafka, making it scalable and flexible for various deployment environments.

+
+
+
+

Zipkin

+
+

Zipkin is a distributed tracing system designed to help developers visualize and diagnose latency issues in microservices-based applications. It provides a lightweight and fast tracing solution, making it ideal for quick deployment with minimal resource usage. Its simplicity and efficiency make it a popular choice for teams looking to implement tracing without significant infrastructure overhead.

+
+
+

One of Zipkin’s core strengths is its tag-based searching, which allows developers to filter traces based on metadata such as service name, request ID, or other custom attributes, enabling quick identification of relevant traces. It also offers dependency graph visualization, helping to uncover bottlenecks and inefficiencies in microservices interactions. To accommodate different storage needs, Zipkin supports multiple storage backends, including Elasticsearch, MySQL, and Cassandra, providing flexibility for various deployment scenarios.

+
+
+
+

Grafana Tempo

+
+

Grafana Tempo is a distributed tracing backend. Unlike Jaeger and Zipkin, Tempo does not require indexing as it only requires object storage, making it highly scalable and cost-efficient for handling large volumes of trace data. This unique approach allows Tempo to store traces efficiently without increasing storage and query overhead, making it an ideal choice for high-performance microservices environments. +One of Tempo’s key advantages is its tight integration with Grafana dashboards, enabling developers to correlate logs, metrics, and traces within a unified observability platform. Additionally, Tempo offers multi-backend support, meaning it can ingest and process trace data from OpenTelemetry, Jaeger, and Zipkin sources, ensuring compatibility with existing tracing setups. Its scalability makes it well-suited for large-scale microservices architectures, where efficiently managing distributed tracing data is crucial.

+
+
+
+
+
+

Exporting the Traces

+
+
+

To export the traces we need to configure the exporter type and endpoint in the src/main/resources/META-INF/microprofile-config.properties. +For using OTLP (OpenTelemetry Protocol) export, you need to add the following configuration in:

+
+
+
+
# Enable OpenTelemetry
+otel.traces.exporter=otlp
+
+# Set the OTLP exporter endpoint
+otel.exporter.otlp.endpoint=http://localhost:4317
+
+# Define the service name
+otel.service.name=payment-service
+
+# Sampling rate: (1.0 = always, 0.5 = 50%, 0.0 = never)
+otel.traces.sampler=parentbased_always_on
+
+
+
+

This sends traces directly to a observability tool, enabling real-time distributed tracing and performance monitoring. To ensure proper tracing, your observability tool (for e.g. Jaeger) must be running to receive trace data.

+
+
+

Using OTLP is advantageous because it is the native standard for OpenTelemetry, ensuring seamless integration with a wide range of observability tools. One of its key benefits is that it allows developers to use multiple observability platforms without changing instrumentation, providing a unified and vendor-neutral tracing solution.

+
+
+

Verify the Traces

+
+

Once tracing is enabled and the appropriate exporter is configured, the next step is to verify that traces are being captured and sent to the observability backend. This ensures that the MicroProfile Telemetry setup is functioning correctly and that distributed tracing data is available for monitoring and debugging.

+
+
+

Run Jaeger

+
+

The simplest way to run Jaeger is with Docker using the command as below:

+
+
+
+
docker run -d --name jaeger \
+  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
+  -p 5775:5775/udp \
+  -p 6831:6831/udp \
+  -p 6832:6832/udp \
+  -p 5778:5778 \
+  -p 16686:16686 \
+  -p 14268:14268 \
+  -p 14250:14250 \
+  -p 9411:9411 \
+  jaegertracing/all-in-one:latest
+
+
+
+

The above command runs the all-in-one Jaeger container, which includes the agent, collector, query service, and UI.

+
+
+

The Jaeger UI can be accessed at: https://<hostname>:16686.

+
+
+

Ensure all the services of our MicroProfile E-commerce applications are running.

+
+
+

Search using parameters like operation name, time range, or service for the traces associated with different microservices and confirm that the telemetry data is visible. +View a detailed breakdown of each span within the trace, including timing and attributes.

+
+
+
+
+
+
+

Types of Telemetry

+
+
+

MicroProfile Telemetry supports multiple approaches to instrumentation and tracing, ensuring flexibility for developers based on their observability needs. The three primary types of telemetry in MicroProfile Telemetry are:

+
+
+

Automatic Instrumentation

+
+

Automatic Instrumentation enables distributed tracing without requiring any modifications to the application code. This is particularly beneficial for Jakarta RESTful Web Services and MicroProfile REST Clients, as it enables seamless integration into distributed tracing systems following the semantic conventions of OpenTelemetry. This ensures compatibility across different tracing tools.

+
+
+

For example, in the ProductService, which exposes a RESTful endpoint, automatic instrumentation ensures that incoming and outgoing HTTP requests are traced with minimal configuration, without requiring any additional code changes.

+
+
+

By default, MicroProfile Telemetry tracing is disabled. To activate it, set the following property in microprofile-config.properties:

+
+
+
+
otel.sdk.disabled=false
+
+
+
+

This ensures that OpenTelemetry’s tracing capabilities are enabled for the application.

+
+
+
+

Manual Instrumentation

+
+

Manual Instrumentation provides developers with fine-grained control over how telemetry data is collected and structured within a MicroProfile application. By explicitly defining spans, attributes, and trace propagation, developers can gain greater insight into application behavior beyond what automatic instrumentation provides.

+
+
+

Using the @WithSpan Annotation

+
+

The @WithSpan annotation provides a simple way to create custom spans within a trace. By annotating a method with @WithSpan, a new span is automatically generated whenever the method is invoked. This span is linked to the current trace context, allowing developers to track key operations without manually managing span lifecycle.

+
+
+
+
import io.opentelemetry.instrumentation.annotations.WithSpan;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class PaymentService {
+
+    @WithSpan
+    public void processPayment(String orderId) {
+        // Business logic here
+    }
+}
+
+
+
+

Every time processPayment is called, a new span is created. The span is automatically linked to the current trace context. No need for explicit span creation or lifecycle management. You can use @WithSpan for tracing key business operations, such as order processing, payment handling, or API requests.

+
+
+
+

Using SpanBuilder for Custom Spans

+
+

For greater flexibility, developers can manually create spans using the OpenTelemetry API. The SpanBuilder class provides the ability to define custom span names, making trace analysis more meaningful and structured. Additionally, developers can attach custom attributes to spans, enriching trace data with relevant metadata for deeper insights. This method also offers explicit control over the span lifecycle, allowing spans to be started and ended manually, ensuring they accurately represent specific business operations or execution flows within the application.

+
+
+
+
import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.api.trace.Span;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+@Path("/trace")
+public class TraceResource {
+
+    @Inject
+    Tracer tracer;
+
+    @GET
+    @Path("/custom")
+    public String customTrace() {
+        Span span = tracer.spanBuilder("custom-span").startSpan();
+        span.setAttribute("custom.key", "customValue");
+        span.end();
+        return "Trace recorded";
+    }
+}
+
+
+
+

The method tracer.spanBuilder("custom-span").startSpan() creates a span with a specific name allowing developers to define meaningful trace segments for better observability. Using span.setAttribute("custom.key", "customValue"), custom metadata can be attached to the span, enriching trace data with relevant contextual information. Finally, calling span.end() explicitly marks the completion of the span, ensuring accurate tracking of execution duration. The SpanBuilder approach is particularly useful when developers require fine-grained control over when spans start and end, as well as the ability to include detailed metadata for enhanced trace analysis.

+
+
+
+
+

Manual Tracing in PaymentService

+
+

To manually instrument the processPayment method in the PaymentService, we use OpenTelemetry’s API to create a custom span, add attributes, and control the span lifecycle.

+
+
+
+
import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class PaymentService {
+
+    @Inject
+    Tracer tracer;
+
+    public void processPayment(String orderId, double amount, String paymentMethod) {
+        // Create a custom span for tracing the payment process
+        Span span = tracer.spanBuilder("payment.process").startSpan();
+
+        try {
+            // Add attributes to enrich the trace
+            span.setAttribute("order.id", orderId);
+            span.setAttribute("payment.amount", amount);
+            span.setAttribute("payment.method", paymentMethod);
+            span.setAttribute("payment.status", "IN_PROGRESS");
+
+            // Business logic for processing the payment
+            System.out.println(“Processing Payment…);
+
+            // Update span attribute on successful completion
+            span.setAttribute("payment.status", "SUCCESS");
+        } catch (Exception e) {
+            // Capture error in tracing
+            span.setAttribute("payment.status", "FAILED");
+            span.recordException(e);
+        } finally {
+            // End the span to complete the tracing
+            span.end();
+        }
+    }
+}
+
+
+
+

The payment.process span is manually created using tracer.spanBuilder(), allowing explicit control over the tracing of the payment process. To enhance trace visibility, custom attributes such as the order ID, payment amount, and payment method are attached to the span, providing valuable context for analysis. Additionally, the payment status is recorded as IN_PROGRESS when processing starts and updated to SUCCESS or FAILED based on the outcome.

+
+
+

In the event of an error, the span captures and records the exception, ensuring failure details are logged for debugging. The span lifecycle is carefully managed, starting before the business logic executes and ending only after the process is completed in the finally block. This structured approach guarantees accurate performance monitoring and trace completeness, improving visibility into how payments are processed in a distributed system.

+
+
+
+
+
+

Agent Instrumentation

+
+
+

Agent Instrumentation enables telemetry data collection without modifying application code by attaching a Java agent at runtime. This approach is particularly useful for legacy applications or scenarios where modifying source code is not feasible. The OpenTelemetry Java Agent dynamically instruments applications, automatically detecting and tracing interactions within commonly used frameworks such as Jakarta RESTful Web Services, database connections, and messaging systems.

+
+
+

One of the key advantages of agent-based instrumentation is that it requires no changes to the application’s source code and eliminates the need for recompilation or redeployment. Instead, it can be activated by attaching the agent at application startup.

+
+
+

Refer to the OpenTelemetry Java Agent Getting Started page for step-by-step instructions on enabling it for your application. +Once enabled, the agent automatically instruments the application, seamlessly integrating with distributed tracing systems without requiring developer intervention. This makes it an efficient and non-intrusive way to implement observability in MicroProfile applications.

+
+
+

Once enabled, the agent automatically instruments the application, seamlessly integrating with distributed tracing systems without requiring developer intervention. This makes it an efficient and non-intrusive way to implement observability in MicroProfile applications.

+
+
+
+
+

Analyzing Traces

+
+
+

Once trace data is collected and exported to a backend system, analyzing these traces becomes a crucial step in understanding the behavior of your distributed microservices architecture. By examining traces, you can gain insights into system performance, identify bottlenecks, and detect failures or anomalies.

+
+
+

Visualizing Traces

+
+

Tracing backends like Jaeger, Zipkin, or Graphana Tempo provide visual interfaces to explore and analyze traces. These tools display traces as timelines or dependency graphs, making it easier to:

+
+
+
    +
  • +

    Understand the sequence of operations.

    +
  • +
  • +

    Identify the services and components involved in a request.

    +
  • +
  • +

    Observe how requests propagate through the system.

    +
  • +
+
+
+
+

Identifying Bottlenecks

+
+

Traces highlight spans with long durations or repeated retries, which often point to bottlenecks or inefficiencies. Pay close attention to:

+
+
+
    +
  • +

    Critical Path: The longest path in a trace that determines the total response time.

    +
  • +
  • +

    Service Dependencies: Examine how upstream and downstream services interact to find slow components.

    +
  • +
  • +

    Retries and Failures: Repeated spans or high failure rates indicate problematic dependencies or transient errors.

    +
  • +
+
+
+
+

Diagnosing Failures

+
+

Traces provide valuable information for diagnosing failures, including:

+
+
+
    +
  • +

    Error Codes: Look for spans with error attributes, such as http.status_code=500.

    +
  • +
  • +

    Exception Details: Many tracing systems capture stack traces or error messages in spans.

    +
  • +
  • +

    Service Impact: Identify which upstream and downstream services are affected by the failure.

    +
  • +
+
+
+
+

Understanding Service Dependencies

+
+

Dependency graphs generated from traces show the interactions between services. These graphs help:

+
+
+
    +
  • +

    Visualize which services depend on each other.

    +
  • +
  • +

    Detects circular dependencies or excessive coupling.

    +
  • +
  • +

    Plan optimizations by focusing on critical services.

    +
  • +
+
+
+
+

Correlating Traces with Logs and Metrics

+
+

Traces, when combined with logs and metrics, provide a comprehensive picture of the system:

+
+
+
    +
  • +

    Logs: Use trace IDs and span IDs in logs to correlate application logs with specific spans.

    +
  • +
  • +

    Metrics: Correlate trace performance data with system metrics like CPU usage, memory consumption, or request rates. +Example: If a span indicates high latency, check corresponding logs and metrics to identify the underlying cause, such as a resource constraint or network delay.

    +
  • +
+
+
+
+

Best Practices for Analyzing Traces

+
+
    +
  1. +

    Establish Baselines: Use traces to establish performance baselines for services.

    +
  2. +
  3. +

    Monitor Critical Paths: Focus on traces that traverse critical services or user-facing operations.

    +
  4. +
  5. +

    Use Sampling Strategically: Balance trace volume and storage costs by sampling traces intelligently.

    +
  6. +
  7. +

    Automate Alerts: Set up alerts for abnormal patterns in traces, such as increased latency or failure rates.

    +
  8. +
  9. +

    Collaborate Across Teams: Share trace insights with development, operations, and QA teams to improve system reliability.

    +
  10. +
+
+
+

By analyzing traces effectively, you can identify opportunities to optimize your microservices, ensure smoother operations, and enhance the overall user experience. Tracing tools provide a powerful way to visualize and understand the intricate dynamics of distributed systems.
+When analyzing traces, developers should look for the following:

+
+
+
    +
  • +

    Long spans: Spans that take a long time to complete may indicate a performance issue.

    +
  • +
  • +

    Missing spans: Missing spans can make it difficult to understand the flow of a request.

    +
  • +
  • +

    Errors: Errors can indicate problems with a service or a request.

    +
  • +
  • +

    High latency: High latency can indicate a problem with the network or a service.

    +
  • +
+
+
+

By analyzing traces, developers can identify and troubleshoot problems with their microservices applications. This can help developers improve the performance and reliability of their applications.

+
+
+

Here are some tips for analyzing traces:

+
+
+
    +
  • +

    Use a trace viewer: A trace viewer is a tool that can help you visualize and analyze traces.

    +
  • +
  • +

    Look for patterns: Look for patterns in the traces that may indicate a problem.

    +
  • +
  • +

    Correlate traces with metrics: Correlate traces with metrics to get a better understanding of the performance of your application.

    +
  • +
  • +

    Use sampling: Use sampling to reduce the number of traces that are collected. This can improve the performance of your tracing system.

    +
  • +
+
+
+

By following these tips, developers can effectively analyze traces to improve the performance and reliability of their microservices applications.

+
+
+
+
+
+

Security Considerations for Tracing

+
+
+

When implementing tracing in your applications, it is crucial to be mindful of security implications. Tracing involves collecting and storing data about application behavior, which can potentially expose sensitive information if not handled properly.

+
+
+
    +
  • +

    Data Sensitivity: Be cautious about the data included in traces. Avoid logging sensitive information such as passwords, API keys, or personally identifiable information (PII).

    +
  • +
  • +

    Access Control: Implement strict access controls to limit who can view and manage trace data.

    +
  • +
  • +

    Encryption: Consider encrypting trace data at rest and in transit to protect it from unauthorized access.

    +
  • +
  • +

    Storage: Carefully manage the storage of trace data. Avoid storing traces indefinitely and implement data retention policies.

    +
  • +
  • +

    Third-Party Services: If using third-party tracing services, ensure they have robust security measures in place to protect your data.

    +
  • +
+
+
+

Avoid Capturing Sensitive Data

+
+

Traces often include attributes and metadata that can contain sensitive information. Avoid storing or transmitting sensitive details, such as:

+
+
+
    +
  • +

    Personally Identifiable Information (PII) (e.g., names, addresses, social security numbers).

    +
  • +
  • +

    Payment information (e.g., credit card numbers).

    +
  • +
  • +

    Authentication credentials (e.g., passwords, API keys, tokens).

    +
  • +
+
+
+

Best Practice:

+
+
+

Sanitize attributes before adding them to spans:

+
+
+
+
span.setAttribute("user.id", "anonymized-user-id");
+span.setAttribute("credit.card.last4", "****1234");
+
+
+
+
+

Encrypt Trace Data

+
+

To prevent unauthorized access during transmission, ensure that telemetry data is encrypted. Use secure protocols such as HTTPS or TLS for exporting trace data to a backend.

+
+
+
+
*Example:*
+
+
+
+
    +
  • +

    Configure the tracing provider to use encrypted connections:

    +
  • +
+
+
+
+
otel.exporter.jaeger.endpoint=https://secure-jaeger-collector.example.com
+otel.exporter.otlp.endpoint=https://secure-collector.example.com
+
+
+
+
+

Limit Trace Retention

+
+

Trace data can grow rapidly in distributed systems. Retaining it indefinitely increases the risk of exposing sensitive information. Implement retention policies to:

+
+
+
    +
  • +

    Retain traces only for the necessary duration for debugging or performance analysis.

    +
  • +
  • +

    Periodically purge older traces from storage.

    +
  • +
+
+
+
+

Access Control and Auditing

+
+

Restrict access to trace data to authorized personnel only. Ensure that your tracing backend implements robust authentication and authorization mechanisms.

+
+
+

Best Practice:

+
+
+
    +
  • +

    Use role-based access control (RBAC) to define permissions for viewing and managing traces.

    +
  • +
  • +

    Audit access to trace data regularly to identify potential misuse or breaches.

    +
  • +
+
+
+
+

Sampling Strategies to Minimize Exposure

+
+

Sampling reduces the volume of traces collected and limits the exposure of sensitive data by capturing only a subset of requests. Common strategies include:

+
+
+
    +
  • +

    Random Sampling: Captures a fixed percentage of traces.

    +
  • +
  • +

    Rate-Limiting Sampling: Limits the number of traces per second.

    +
  • +
  • +

    Key-Based Sampling: Samples traces based on specific attributes (e.g., user ID).

    +
  • +
+
+
+

Example:

+
+
+

Random sampling to limiting the amount of trace data collected:

+
+
+
+
otel.traces.sampler=traceidratio
+otel.traces.sampler.traceidratio=0.1
+
+
+
+
+

Compliance with Regulations

+
+

Ensure that your tracing practices comply with data protection and privacy regulations such as GDPR, CCPA, or HIPAA. Key considerations include:

+
+
+
    +
  • +

    Anonymizing sensitive data before tracing.

    +
  • +
  • +

    Informing users about telemetry collection in your privacy policy.

    +
  • +
  • +

    Providing mechanisms to opt out of tracing where required.

    +
  • +
+
+
+
+

Isolate Tracing Infrastructure

+
+

The tracing infrastructure, such as Jaeger or OpenTelemetry Collector, should be isolated from the public internet and accessible only within secure networks.

+
+
+

Best Practice:

+
+
+
    +
  • +

    Deploy tracing backends in private subnets or behind firewalls.

    +
  • +
  • +

    Use VPNs or dedicated connections for remote access to tracing dashboards.

    +
  • +
+
+
+
+

Monitor and Alert on Trace Anomalies

+
+

Tracing can help detect potential security incidents. Monitor traces for unusual patterns, such as:

+
+
+
    +
  • +

    Unexpected spikes in requests.

    +
  • +
  • +

    Requests from unknown or unauthorized sources.

    +
  • +
  • +

    Abnormal response times indicating possible exploits. +Set up alerts for these anomalies to investigate and mitigate potential issues.
    +By following these security considerations, you can leverage the benefits of distributed tracing without compromising the security of your system or the privacy of your users. Careful handling of trace data, coupled with robust encryption, access controls, and compliance practices, ensures that tracing remains a valuable yet secure component of your observability strategy.

    +
  • +
+
+
+
+
+
+

Conclusion

+
+
+

MicroProfile Telemetry provides a robust foundation for observability in Java-based microservices, enabling developers to implement distributed tracing seamlessly. By leveraging this specification, you can gain deep insights into the flow of requests, identify bottlenecks, and enhance the reliability and performance of your applications. The integration of standardized tracing concepts like spans, traces, and context propagation ensures that developers can maintain a cohesive understanding of their system’s behavior across service boundaries.

+
+
+

Through instrumentation, context propagation, and effective trace analysis, MicroProfile Telemetry simplifies the complexities of monitoring and debugging distributed systems. It empowers teams to proactively address issues, optimize performance, and improve the user experience. Moreover, by adhering to security best practices, developers can ensure that telemetry data is protected, compliant with regulations, and free of sensitive information.

+
+
+

In this chapter, we explored the critical security considerations surrounding tracing within the MicroProfile Telemetry framework. We emphasized the importance of safeguarding sensitive data by avoiding the inclusion of Personally Identifiable Information (PII) in trace spans. Additionally, we discussed the potential security risks associated with tracing in production environments and the significance of carefully managing sampling rates and data retention policies. By adhering to these security best practices, developers can harness the power of tracing for observability while ensuring the confidentiality and integrity of their applications.

+
+
+

As microservices architectures continue to evolve, the ability to observe and trace system interactions will remain a critical factor in maintaining resilient and efficient applications. MicroProfile Telemetry stands as a valuable tool in achieving these goals, providing developers with the observability they need to deliver reliable, high-performance microservices in modern cloud-native environments.

+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter10/chapter10.html b/build/site/microprofile-tutorial/6.1/chapter10/chapter10.html new file mode 100644 index 00000000..b952d529 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter10/chapter10.html @@ -0,0 +1,972 @@ + + + + + +JWT Authentication :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

JWT Authentication

+
+
+
+

In modern microservices architectures, where services are distributed and stateless, +securing communications between clients and services and between individual services is critical. JSON Web Token (JWT) provides a lightweight, self-contained, and efficient user authentication and authorization mechanism, enabling scalable and secure identity propagation across distributed systems.

+
+
+

MicroProfile JWT is a specification that standardizes JWT-based authentication and authorization for Java microservices. Leveraging the JWT open standard RFC 7519 enables services to securely extract and validate claims such as identity, roles, and permissions.

+
+
+

MicroProfile JWT allows developers to build secure, interoperable, and portable microservices. It supports role-based access control (RBAC), simplifies identity management in stateless services, and avoids vendor lock-in by adhering to open specifications.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Introduction to JWT Authentication

    +
  • +
  • +

    Structure of JWT

    +
  • +
  • +

    Use cases for JSON Web Tokens

    +
  • +
  • +

    Benefits of JWT in Microservices

    +
  • +
  • +

    Setting up MicroProfile JWT

    +
  • +
  • +

    Configuring MicroProfile JWT Validation

    +
  • +
  • +

    Request Flow in MicroProfile JWT

    +
  • +
  • +

    Role-Based Access Control (RBAC)

    +
  • +
  • +

    Setting Token Expiry Times for Security

    +
  • +
  • +

    Error Handling

    +
  • +
  • +

    Best Practices for JWT Authentication

    +
  • +
  • +

    Security Best Practices for Microservices

    +
  • +
  • +

    Conclusion

    +
  • +
+
+
+
+
+

Introduction to JWT Authentication

+
+
+

This section will explore JSON Web Tokens, how they work, and why they are foundational to implementing stateless authentication and authorization in microservices-based systems.

+
+
+

What is a JSON Web Token (JWT)?

+
+

A JSON Web Token (JWT) (see JWT.io), as defined in RFC 7519, is an open standard for securely transmitting information (claims) between parties as a JSON object. JWTs are digitally signed, ensuring their integrity and authenticity.

+
+
+
+

Structure of a JWT

+
+

A JWT consists of three Base64 encoded parts, separated by dots (.):

+
+
+
+
<Header>.<Payload>.<Signature>
+
+
+
+
    +
  • +

    Header - It contains metadata about the token, such as token type (type: “JWT”) and signing algorithm ( alg: “RS256” for RSA-SHA256).

    +
  • +
+
+
+
+
{
+  "alg": "RS256",
+  "typ": "JWT"
+}
+
+
+
+
    +
  • +

    Payload - It contains claims which are key-value pairs representing data about the user, such as roles and expiration. +Example of claims in a JWT payload:

    +
  • +
+
+
+
+
{
+  "iss": "https://io.microprofile.com/issuer",
+  "sub": "user1",
+  "exp": 1735689600,
+  "iat": 1735686000,
+  "aud": "my-audience",
+  "groups": ["user", "admin"]
+}
+
+
+
+
    +
  • +

    Signature — A digital signature that verifies the token’s integrity by combining the encoded header, payload, and private key.

    +
  • +
+
+
+

Example JWT Token:

+
+
+
+
eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.
+QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM
+oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG
+TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima
+sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52
+YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a
+1rZgN5TiysnmzTROF869lQ.
+AxY8DCtDaGlsbGljb3RoZQ.
+MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM
+HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8.
+fiK51VwhsxJ-siBMR-YFiA
+
+
+
+
+

Types of Claims in MicroProfile JWT

+
+

Claims in JWTs can be categorized into two types:

+
+
+

Standard Claims

+
+

These are predefined claims with specific meanings, as defined by the JWT specification. Some commonly used standard claims include:

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClaimDescriptionExample

iss

Issuer, the entity that issued the JWT (e.g., an authentication server)

"iss": "https://io.microprofile.com/issuer"

sub

Subject, the principal (user or service) that the JWT is about.

"sub": "user1"

aud

Audience, the intended recipients of the token (e.g., specific microservices).

"aud": "order-service"

exp

Expiration time

"exp": 1735689600

nbf

not before

"nbf": 1735686000

iat

issued at, when that token was issued.

"iat": 1735686000

jti

Unique JWT token identifier

"jti": "a1b2c3d4"

groups

Groups, list of roles or users allowed to access the resource

["user", "admin"]

+
+
+

Custom Claims

+
+

These are application-specific claims that provide additional information about the user or entity. They can extend authorization logic with application-specific claims (e.g., department, region). Custom claims are not part of the JWT specification but often include domain-specific data, such as user preferences, tenant IDs, or other metadata. MicroProfile JWT allows developers to access these claims programmatically.

+
+
+
+
+
+
+

Use cases for JSON Web Tokens

+
+
+

JWTs are versatile tokens commonly used in modern applications for authentication, where they verify the identity of a user or service; for authorization, where they grant access to resources based on roles or permissions; and for information exchange, where they securely transmit data between parties.

+
+
+

Below are key scenarios where JWTs shine in microservices environments:

+
+
+

Authentication

+
+

JWTs enable stateless authentication in distributed systems. When a user logs in, an authentication service issues a JWT containing claims like sub (user ID) and exp (expiration time). The client sends this token in the Authorization: Bearer header of subsequent requests, allowing microservices to verify the user’s identity without requiring repeated authentication.

+
+
+

For example, a user authenticates with an Auth Service and receives a JWT. This JWT token grants access to other services, such as a product catalog or order management system, without re-authentication.

+
+
+
+

Authorization (Role-Based Access Control)

+
+

JWTs are also used for authorization, enabling fine-grained access control based on user roles or permissions. The JWT payload typically includes a group or role claim specifying the user’s roles or permissions. For example, a user with the admin role might be allowed to access all resources while a user with the user role might only have access to specific resources.

+
+
+

MicroProfile JWT integrates seamlessly with Jakarta EE’s @RolesAllowed annotation, making it easy to enforce role-based access control (RBAC) in microservices. Role mapping can be configured in microprofile-config.properties:

+
+
+
+
mp.jwt.verify.roles=groups
+
+
+
+
+

Claims-based identity

+
+

JWTs are often used to represent claims-based identity, where the JWT contains claims representing the user’s identity, such as their name, email address, or other attributes. Applications can use these claims to identify the user and personalize their experience.

+
+
+

For example, an application might use the email claim to look up the user’s profile information in a database or +display the user’s name on a welcome page using the name claim.

+
+
+
+

Information Exchange

+
+

JWTs can securely exchange information between parties. The token payload can include custom claims representing the data being exchanged, such as an order ID or user ID. This makes JWTs useful in scenarios like Single Sign-On (SSO) systems, where information needs to be shared across multiple services.

+
+
+

For example, a JWT might contain an order_id claim and a user_id claim, which an order management service can use to retrieve and display the user’s order details.

+
+
+
+

Federation & Single Sign-On (SSO)

+
+

JWTs facilitate identity federation by allowing integration of multiple trusted identity providers (e.g., Active Directory, LDAP) to provide a single sign-on (SSO) experience. In this case, the JWT contains claims representing the user’s identity, which applications can use to identify the user and retrieve their profile information.

+
+
+

For example, an enterprise SSO system can issue a JWT that grants access to HR, Payroll, and CRM microservices. MicroProfile JWT validates the token’s iss (issuer) and aud (audience) to enforce trust boundaries.

+
+
+
+
+
+

Benefits of using JWT in Microservices

+
+
+

JWTs are widely used in microservices for the following reasons:

+
+
+

Statelessness & Scalability

+
+

JWTs eliminate the need for centralized session storage. Each token is self-contained, embedding all necessary user claims (e.g., roles, permissions) in its payload.

+
+
+

Independent Validation: Microservices validate JWTs locally using public keys, avoiding calls to a central authority. This reduces latency and scales horizontally.

+
+
+

Example: +A payment service validates a JWT’s signature without querying an authentication server.

+
+
+
+

Interoperability

+
+

Open Standards: JWTs adhere to RFC 7519, ensuring compatibility across platforms (Java, .NET, Node.js) and frameworks (Spring Boot, Quarkus).

+
+
+

MicroProfile Integration: MicroProfile JWT standardizes validation and claim extraction, enabling seamless interoperability across Java microservices.

+
+
+
+

Fine-Grained Authorization

+
+

Role-Based Access Control (RBAC): Map JWT claims (e.g., groups) to Jakarta EE roles using @RolesAllowed.

+
+
+
+

Decentralized Security

+
+

Propagation Across Services: A JWT issued by an authentication service is propagated across microservices (e.g., the Order Service and the Inventory Service). Each service independently verifies the token and enforces access control.

+
+
+

Reduced Central Dependency: No need for a central authorization server, simplifying architecture and improving +fault tolerance.

+
+
+

Example:

+
+
+
    +
  • +

    Authentication Service: Issues a JWT with sub: "user1" and groups: ["user"].

    +
  • +
  • +

    Order Service: Validates the JWT and processes requests if groups include users.

    +
  • +
  • +

    Inventory Service: Revalidates the same JWT without contacting the auth service.

    +
  • +
+
+
+
+
+
+

Setting Up MicroProfile JWT

+
+
+

To use MicroProfile JWT in your project, add the following dependency to your pom.xml (for Maven):

+
+
+
+
<dependency>
+    <groupId>org.eclipse.microprofile.jwt</groupId>
+    <artifactId>microprofile-jwt-auth-api</artifactId>
+    <version>2.1</version>
+    <scope>provided</scope>
+</dependency>
+
+
+
+

For Gradle, add the following to your build.gradle:

+
+
+
+
implementation 'org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1'
+
+
+
+
+
+

Configuring MicroProfile JWT Validation

+
+
+

MicroProfile JWT requires validation rules configuration to be defined in src/main/resources/microprofile-config.properties file. Below is an example configuration:

+
+
+
+
# Public key (PEM format) to verify JWT signatures
+mp.jwt.verify.publickey.location=META-INF/publicKey.pem
+
+# Expected issuer (e.g., your OIDC provider)
+mp.jwt.verify.issuer=https://auth.example.com
+
+# Optional: Validate token audience
+mp.jwt.verify.audiences=order-service,payment-service
+
+
+
+

Explanation:

+
+
+
    +
  • +

    The mp.jwt.verify.publickey.location property specifies the location of the public key used to verify the JWT’s signature.

    +
  • +
  • +

    The mp.jwt.verify.issuer property defines the expected issuer of the JWT, ensuring that tokens are only accepted if issued by a trusted authority.

    +
  • +
  • +

    Optionally, the mp.jwt.verify.audiences property can specify the allowed audiences for the JWT, ensuring that the token is intended for the service.

    +
  • +
+
+
+

Public Key Setup

+
+

Place the PEM-encoded public key in src/main/resources/META-INF/publicKey.pem. This key is used to verify incoming JWT signatures.

+
+
+
+
+
+

Request Flow in MicroProfile JWT

+
+
+

Understanding how JWTs are propagated and processed in a microservices architecture is critical to implementing secure and scalable authentication. This section explains the lifecycle of a JWT from client to service, including token extraction, validation, and claim usage.

+
+
+

How JWTs are Propagated in Microservices

+
+

JWTs are propagated via the Authorization: Bearer HTTP header across clients and services.

+
+
+

Client-to-Service

+
+

When a client authenticates (e.g., via a login endpoint), it receives a JWT from an authentication service. This token is then included in the header of subsequent requests to microservices. For example, a request header might look like this:

+
+
+
+
GET /api/orders HTTP/1.1
+Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
+
+
+
+
+

Service-to-Service

+
+

In microservices architecture, a client sends a JWT token to the initial service (for example, Order Service) using the Authorization: Bearer header. The initial service can forward the same token when calling another backend service (for example, the Inventory Service). Each microservice independently validates the JWT to enforce decentralized, stateless security.

+
+
+

In advanced scenarios involving multiple downstream services, careful consideration must be given to validating the JWT’s aud (audience) claim to ensure the token is intended for the target service.

+
+
+
+

Token Extraction

+
+

MicroProfile JWT runtime handles token extraction and validation automatically. The token is parsed and validated as follows:

+
+
+
    +
  • +

    Header Parsing: The runtime extracts the token from the Bearer schema.

    +
  • +
  • +

    Decoding: The JWT is split into its header, payload, and signature components.

    +
  • +
+
+
+
+

Token Validation

+
+

The token validation involves the following steps:

+
+
+
    +
  • +

    Signature Verification: The public key validates the token’s integrity.

    +
  • +
  • +

    Standard Claims Validation: The runtime then validates standard claims:

    +
    +
      +
    1. +

      iss: It should match the mp.jwt.verify.issuer configuration property.

      +
    2. +
    3. +

      exp : This checks if the token has not expired.

      +
    4. +
    5. +

      aud : Optionally it checks for the included service(s) in mp.jwt.verify.audiences.

      +
    6. +
    +
    +
  • +
+
+
+

If valid, the JWT’s claims populate the SecurityContext. Otherwise, MicroProfile JWT rejects the request with a 401 Unauthorized status.

+
+
+
+
+

Accessing JWT claims via SecurityContext

+
+

The SecurityContext interface (from Jakarta EE) provides programmatic access to JWT claims. Once a token is validated, MicroProfile JWT injects the JsonWebToken into the SecurityContext, allowing developers to:

+
+
+
    +
  • +

    Retrieve user identity (e.g., sub claim).

    +
  • +
  • +

    Check user roles (e.g., groups claim).

    +
  • +
  • +

    Access custom claims (e.g., tenant_id claim).

    +
  • +
+
+
+
+
@GET
+@Path("/user-profile")
+public String getUserProfile(@Context SecurityContext ctx) {
+    JsonWebToken jwt = (JsonWebToken) ctx.getUserPrincipal();
+    String userId = jwt.getName(); // Extracts the "sub" claim
+    Set<String> roles = jwt.getGroups(); // Extracts the "groups" claim
+    String tenant = jwt.getClaim("tenant_id"); // Custom claim
+
+    return "User: " + userId + ", Roles: " + roles + ", Tenant: " + tenant;
+}
+
+
+
+

The SecurityContext simplifies working with JWTs, enabling seamless integration with Jakarta EE’s security annotations like @RolesAllowed. By calling securityContext.getUserPrincipal(), the application can obtain the JsonWebToken instance, which contains all the claims from the JWT.

+
+
+
+
+
+

Role-Based Access Control (RBAC)

+
+
+

MicroProfile JWT simplifies RBAC by mapping JWT claims (e.g., groups or roles) to Jakarta EE roles. This enables declarative security using the @RolesAllowed annotation. This section explains how to configure and use this mapping effectively.

+
+
+

Default Role Mapping with the groups Claim

+
+

MicroProfile JWT seamlessly integrates with Jakarta EE’s @RolesAllowed annotation to enforce role-based access control in microservices. By default, MicroProfile JWT maps roles from the groups claim in the JWT payload to Jakarta EE roles. The groups claim is a standard JWT claim that represents the roles or groups assigned to the user. For example, a JWT payload might include:

+
+
+
+
{
+  "iss": "https://example.com/issuer",
+  "sub": "user123",
+  "groups": ["user", "admin"]
+}
+
+
+
+

In this case, the user has two roles: user and admin.

+
+
+
+

Securing Endpoints

+
+

The roles in the groups claim can be used directly with the @RolesAllowed annotation to secure endpoints.

+
+
+
+
@Path("/orders")
+public class OrderResource {
+
+  @GET
+  @Path("/{id}")
+  @RolesAllowed("user") // Only users can access this method
+  public Response getOrder(@PathParam("id") String id, @Context SecurityContext ctx) {
+    String user = ctx.getUserPrincipal().getName();
+    // Fetch order for the user
+    return Response.ok("Order for user: " + user + ", ID: " + id).build();
+  }
+
+  @DELETE
+  @Path("/{id}")
+  @RolesAllowed("admin") // Only admins can access this method
+  public Response deleteOrder(@PathParam("id") String id, @Context SecurityContext ctx) {
+    String admin = ctx.getUserPrincipal().getName();
+    // Delete order as admin
+    return Response.ok("Order deleted by admin: " + admin + ", ID: " + id).build();
+  }
+}
+
+
+
+

The GET /orders/chapter10 service is accessible to users, whereas the DELETE /orders/chapter10 is only available to users with the admin role.

+
+
+
+

Custom Role Mapping

+
+

If your JWT uses a claim other than groups to represent roles (e.g., roles or scopes), you can customize the mapping using the mp.jwt.verify.roles property in microprofile-config.properties:

+
+
+
+
# Optional: Map roles from the "groups" claim (default behavior)
+mp.jwt.verify.roles=groups
+
+
+
+

The groups claim is the default claim used for role mapping in MicroProfile JWT Authentication. Therefore, you typically do not need to set the mp.jwt.verify.roles property unless your JWT uses a different claim name. For example, some identity providers (like OAuth 2.0 servers or OpenID Connect providers) might include roles in claims such as roles, permissions, or scope instead of groups.

+
+
+

In such cases, update the mapping in your microprofile-config.properties file:

+
+
+
+
mp.jwt.verify.roles=roles
+
+
+
+

This ensures that MicroProfile JWT Authentication correctly maps the roles for use with Jakarta EE security annotations like @RolesAllowed.

+
+
+

How the RBAC Works

+
+
    +
  • +

    Token Validation: MicroProfile JWT validates the JWT’s signature and claims.

    +
  • +
  • +

    Role Extraction: Roles are extracted from the configured claim (groups by default).

    +
  • +
  • +

    Access Control: The @RolesAllowed annotation checks if the user’s roles match the required roles. If not, a 403 Forbidden response is returned.

    +
  • +
+
+
+

This approach ensures fine-grained security while maintaining compatibility with standard JWT practices.

+
+
+
+
+
+
+

Setting Token Expiry Times for Security

+
+
+

Short token expiry times reduce the surface area for the attackers. Here’s how to configure token expiry effectively:

+
+
+

Configuring Token Expiry

+
+

Set the exp claim at issuance: Ensure your authentication service issues tokens with the exp claim.

+
+
+
+
{
+  "exp": 1735689600 // Token expires at 2025-01-01 00:00:00 UTC
+}
+
+
+
+

MicroProfile JWT automatically validates the exp claim during token verification. Beyond standard JWT validation settings, no additional configuration is needed.

+
+
+

MicroProfile JWT will reject tokens returning a 401 Unauthorized response if:

+
+
+
    +
  • +

    The exp claim is missing or invalid.

    +
  • +
  • +

    The current time exceeds the exp value.

    +
  • +
+
+
+
+
+
+

Error Handling

+
+
+

MicroProfile JWT automatically validates tokens and rejects invalid requests with standardized HTTP responses. Common scenarios include:

+
+
+

Invalid Token (e.g., malformed JWT, invalid signature):

+
+
+
HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Bearer error="invalid_token"
+
+
+
+
+

Expired Token (exp claim validation failure):

+
+
+
HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Bearer error="invalid_token", error_description="Token expired"
+
+
+
+
+

Missing Token

+
+
+
HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Bearer error="missing_token"
+
+
+
+
+

Insufficient Permissions (e.g., missing role for @RolesAllowed):

+
+
+
HTTP/1.1 403 Forbidden
+
+
+
+
+

Best Practices for JWT Authentication

+
+
    +
  1. +

    Use Standard Claims - Prefer the groups claim for roles unless your identity provider uses a different claim.

    +
  2. +
  3. +

    Consistent Role Names - Ensure role names (e.g., admin, user) are consistent across JWTs and @RolesAllowed annotations.

    +
  4. +
  5. +

    Least Privilege - Assign minimal required roles to endpoints to reduce security risks.

    +
  6. +
  7. +

    Combine with Other Annotations - Use @PermitAll or @DenyAll alongside @RolesAllowed for flexible security policies.

    +
  8. +
+
+
+
+
+
+

Security Best Practices for Microservices

+
+
+

But with more services comes more complexity, and with more complexity comes a greater risk of security breaches. So, how do you secure your microservices?

+
+
+

Securing microservices requires a layered approach, combining authentication, authorization, encryption, and monitoring. MicroProfile JWT simplifies access control while adhering to industry standards. Below are best practices tailored for MicroProfile JWT implementations:

+
+
+
    +
  1. +

    Enforce Authentication with Validated JWTs: Ensure every request to a microservice includes a valid JWT. Configure MicroProfile JWT to validate tokens using a public key. Reject tokens with invalid signatures, missing claims, or expired exp values.

    +
  2. +
  3. +

    Implement Role-Based Access Control: Restrict endpoint access based on user roles defined in the JWT. Configure role mapping in microprofile-config.properties if using non-default claims

    +
  4. +
+
+
+

Use Short-Lived Tokens: To minimize exposure to compromised tokens, set short expiration times (exp claim) for JWTs (e.g., 15–30 minutes).

+
+
+
    +
  1. +

    Secure Token Transmission: Prevent token interception or tampering by using HTTPS to encrypt data in transit and store tokens in HTTP Authorization: Bearer headers (never in URLs or cookies).

    +
  2. +
  3. +

    Manage Cryptographic Keys Securely: Protect keys to sign/verify JWTs by storing public keys in secure locations (e.g., Kubernetes Secrets, AWS KMS). Rotate keys periodically and avoid hardcoding them in source control.

    +
  4. +
  5. +

    Validate and Sanitize JWT Claims: Validate all claims (e.g., iss, aud) in microprofile-config.properties, and Sanitize custom claims before use to prevent injection attacks and misuse of claims.

    +
  6. +
  7. +

    Monitor and Log Security Events: Log JWT validation errors, role mismatches, and token expiration events to detect breaches and audit access patterns. Integrate with monitoring tools (e.g., Prometheus, Grafana) to track anomalies.

    +
  8. +
+
+
+

These steps will help you secure your microservices against the most common attacks.

+
+
+
+
+

Conclusion

+
+
+

MicroProfile JWT offers a standards-based, interoperable approach for securing microservices. It simplifies identity propagation, access control, and stateless security across distributed services. Integrating with Jakarta EE enables secure, scalable, and interoperable authentication without a session state.

+
+
+

Further Reading:

+
+ +
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/chapter11/chapter11.html b/build/site/microprofile-tutorial/6.1/chapter11/chapter11.html new file mode 100644 index 00000000..43fa8f19 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/chapter11/chapter11.html @@ -0,0 +1,926 @@ + + + + + +MicroProfile Rest Client :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile Rest Client

+
+
+
+

In microservices architecture, developers often face the cumbersome task of implementing boilerplate code to consume REST APIs - manually constructing HTTP requests, parsing responses, and handling errors. The MicroProfile Rest Client specification addresses this by leveraging Jakarta RESTful Web Services (formerly JAX-RS) annotations to create type-safe Rest client interfaces. Instead of writing low-level HTTP logic, developers define Java interfaces that mirror the target service’s endpoints. At runtime, MicroProfile Rest Client dynamically generates an implementation of these interfaces, automating HTTP communication while ensuring compile-time consistency between the client and server contracts.

+
+
+

This chapter introduces the MicroProfile Rest Client, a type-safe framework for simplifying service-to-service communication. We will begin by defining REST client interfaces using Jakarta RESTful Web Services annotations (@GET, @Path), configuring endpoints via MicroProfile Config, and implementing HTTP invocation. Next, we will explore handling HTTP communication, processing responses, and error handling. By the end of this chapter, you will be able to replace hand-written HTTP boilerplate code with declarative, maintainable clients while adhering to Jakarta EE and MicroProfile standards.

+
+
+
+
+

Topics to be covered:

+
+
+
    +
  • +

    Introduction to MicroProfile Rest Client

    +
  • +
  • +

    Setting up Dependencies

    +
  • +
  • +

    Defining a Rest Client Interface

    +
  • +
  • +

    Parameter Configuration

    +
  • +
  • +

    Requests and Response Handling

    +
  • +
  • +

    Working with JSON Data formats

    +
  • +
  • +

    Error Handling Strategies

    +
  • +
+
+
+
+
+

Introduction to MicroProfile Rest Client

+
+
+

The MicroProfile Rest Client specification simplifies RESTful service consumption in Java microservices by replacing error-prone manual HTTP handling with a type-safe, annotation-driven approach. Instead of writing boilerplate code, developers define Java interfaces that mirror the target service’s API. Using Jakarta RESTful Web Services annotations like @GET, and @Path, these interfaces declaratively map methods to HTTP operations (e.g., /users/{id} to getUser(id)). The framework then generates an implementation at runtime, automating communication while ensuring compile-time consistency between client and server contracts. Tight integration with MicroProfile Config and CDI allows seamless configuration and injection, making it ideal for building resilient, maintainable clients that align with modern microservices practices.

+
+
+
+
+

Key Features of MicroProfile Rest Client

+
+
+

The MicroProfile Rest Client simplifies consuming RESTful services in Java microservices with the following key features:

+
+
+
    +
  1. +

    Type-Safe and Declarative APIs - The MicroProfile Rest Client allows developers to define REST clients as Java interfaces using Jakarta RESTful Web Services annotations like @GET, @POST, @PUT, @DELETE, @Path, @Consumes and @Produces. This approach improves code clarity and ensures compile-time validation, reducing the possibility of runtime errors .

    +
  2. +
  3. +

    Integration with CDI (Context and Dependency Injection) - This specification allows developers to seamlessly inject MicroProfile Rest Client interfaces using @Inject and @RestClient into CDI-managed beans, promoting better dependency management and integration with other components. By leveraging CDI lifecycle management, the MicroProfile Rest Client can benefit from scope management (e.g., @ApplicationScoped), proxying, and automatic initialization.

    +
  4. +
  5. +

    Runtime Configurable with MicroProfile Config - The behavior of MicroProfile Rest Client can be dynamically configured using MicroProfile Config. This allows properties like the base URL and other client settings to be adjusted without recompilation. The configuration can be provided through microprofile-config.properties or environment variables, making the client highly adaptable to different environments.

    +
  6. +
  7. +

    Support for Asynchronous Execution - For asynchronous execution, MicroProfile Rest Client can return CompletionStage<T>, allowing non-blocking requests. This significantly improves performance & scalability in high-concurrency environments.

    +
  8. +
  9. +

    Automatic Handling of Redirect Responses - MicroProfile Rest Client can automatically follow HTTP redirects, simplifying client implementation when working with services that return 3xx responses.

    +
  10. +
  11. +

    Secure Socket Layer (SSL) and Security Configuration - Supports SSL/TLS configuration, including certificates and trust stores, ensuring secure communication between microservices.

    +
  12. +
  13. +

    Propagation of Headers and Cookies - Enables automatic propagation of HTTP headers, cookies and context (e.g., authentication tokens), facilitating session management across service calls.

    +
  14. +
  15. +

    Exception Handling and Custom Providers - Allows custom exception mapping and response handling, giving developers control over error response based on specific conditions, improving fault tolerance and user experience.

    +
  16. +
  17. +

    Integration with MicroProfile Fault Tolerance - This specification Supports resilience patterns like retries (@Retry), circuit breakers (@CircuitBreaker), and Bulkheads (@Bulkhead), ensuring stability in service-to-service communications.

    +
  18. +
  19. +

    Integration with MicroProfile Long Running Actions (LRA) - MicroProfile Rest Client can coordinate distributed transactions using LRA annotations (e.g., @LRA), enabling compensation logic for long-running processes. This ensures consistency across services in complex workflows.

    +
  20. +
  21. +

    Portability and Standards Compliance: This specification enables MicroProfile Rest Client to work across different MicroProfile-compatible runtimes, leveraging Jakarta EE standards (CDI, Jakarta RESTful Web Services, JSON Binding, JSON Processing).

    +
  22. +
+
+
+
+
+

Setting up Dependency for MicroProfile Rest Client

+
+
+

To use MicroProfile Rest Client 3.1 in your project, you need to include the necessary dependencies in your build configuration. Below are configurations for Maven and Gradle:

+
+
+

Maven Configuration

+
+

For Maven-based projects, add the following dependency to your pom.xml file:

+
+
+
+
<dependency>
+    <groupId>org.eclipse.microprofile.rest.client</groupId>
+    <artifactId>microprofile-rest-client-api</artifactId>
+    <version>3.1</version>
+</dependency>
+
+
+
+
+

Gradle Configuration

+
+

For Gradle-based projects, add the following dependency to your build.gradle file:

+
+
+
+
dependencies {
+    Implementation 'org.eclipse.microprofile.rest.client:microprofile-rest-client-api:3.1'
+    compileOnly 'org.eclipse.microprofile:microprofile:6.1'
+}
+
+
+
+
+
+

Tip: The MicroProfile Rest Client is an Eclipse Foundation project. For more details and updates on the project, visit the official repository: MicroProfile Rest Client on GitHub.

+
+
+
+
+
+
+
+

Creating MicroProfile Rest Client Interface

+
+
+

To create a MicroProfile Rest Client interface, you need to define a Java interface and annotate it with annotations to map it to a RESTful service.

+
+
+

The @RegisterRestClient Annotation

+
+

To use the MicroProfile Rest Client, annotate your client interface with @RegisterRestClient. This annotation registers the interface as a Rest client within MicroProfile runtime and enables it as a CDI bean, allowing it to be injected into other components.

+
+
+

Example:

+
+
+
+
package io.microprofile.tutorial.inventory.client;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import io.microprofile.tutorial.inventory.dto.Product;
+
+@RegisterRestClient(configKey = "product-service")
+@Path("/products")
+public interface ProductServiceClient {
+
+    @GET
+    @Path("/{id}")
+    Product getProductById(@PathParam("id") Long id);
+}
+
+
+
+

Explanation: +In the above code, we define a ProductServiceClient within the package io.microprofile.tutorial.inventory.client. The interface serves as a Rest client for interaction with a remote product service.

+
+
+
    +
  1. +

    @RegisterRestClient - declares the ProductServiceClient interface as a MicroProfile Rest Client, enabling it to be injected into other CDI-managed components.

    +
  2. +
  3. +

    configKey = "product-service" - associates the client with a configuration key, allowing dynamic configuration via MicroProfile Config (e.g., using microprofile-config.properties or environment variables).

    +
  4. +
  5. +

    @Path(/products) - specifies the base URI path segement for the RESTful service.

    +
  6. +
  7. +

    @GET - indicates that the getProductById() method handles HTTP GET requests.

    +
  8. +
  9. +

    @Path("/{id}") – define a dynamic URI path parameter {id}, which will be replaced at runtime with the actual value provided.

    +
  10. +
  11. +

    @PathParam("id") - binds the method parameter id to the {id} placeholder in the request URL.

    +
  12. +
  13. +

    Return Type (Product) - specifies that the method returns a Product Data Transfer Object (DTO), representing the retrieved product data.

    +
  14. +
+
+
+
+
+

Note: In CDI environments, it is recommended not to extend AutoCloseable in REST client interfaces. The container manages the lifecycle of injected clients automatically, ensuring proper resource handling without requiring manual closure.

+
+
+
+
+

Configuration via MicroProfile Config:

+
+

To configure the URI using MicroProfile Config, you need to add a config file named src/main/webapp/META-INF/microprofile-config.properties in your project. This file contains the configuration key and value pairs. In this example, we’re configuring the base URI to http://localhost:8080/api/products. We can configure other client properties, such as followRedirects. The followRedirects property specifies whether the client should automatically follow HTTP redirects (3xx status codes) when making RESTful web service calls.

+
+
+
+
product-service/mp-rest/url=http://localhost:8080/api/products
+product-service/mp-rest/followRedirects=true
+
+
+
+
+
+
+
+

Parameter Configurations

+
+
+

In MicroProfile Rest Client, you can dynamically configure headers, query parameters, and path parameters using Jakarta RESTful Web Services annotations. These annotations bind method parameters to different parts of the HTTP request, enabling flexible and dynamic RESTful client interfaces that can efficiently interact with various endpoints.

+
+
+

Supported Parameter Annotations

+
+
+
    +
  1. +

    @PathParam – Binds a method parameter to a path variable in the URL.

    +
  2. +
  3. +

    @QueryParam – Maps a method parameter to a query string parameter in the request URL.

    +
  4. +
  5. +

    @HeaderParam – Attaches a method parameter to an HTTP request header.

    +
  6. +
+
+
+

Using Path Parameters (@PathParam)

+
+

Path parameters are used to insert dynamic values directly into the URL path.

+
+
+
+
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+@Path("/products")
+public interface ProductServiceClient {
+
+    @GET
+    @Path("/{id}")
+    Product getProductById(@PathParam("id") Long id);
+}
+
+
+
+

Example

+
+
+
+
productServiceClient.getProductById(1L);
+
+
+
+

Resulting HTTP Request

+
+
+
+
GET /products/1
+
+
+
+

Why Use @PathParam?

+
+
    +
  1. +

    Ensures URL structure consistency by enforcing path variables

    +
  2. +
  3. +

    Prevents hardcoding URLs, making the code cleaner and maintainable.

    +
  4. +
+
+
+
+
+

Using Query Parameters (@QueryParam)

+
+

Query parameters are typically used for filtering, pagination, or optional parameters in the request URL.

+
+
+

Example:

+
+
+
+
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.QueryParam;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+@Path("/products")
+public interface ProductServiceClient {
+
+    @GET
+    List<Product> getProductsByCategory(@QueryParam("category") String category);
+}
+
+
+
+

Example Call:

+
+
+
+
productServiceClient.getProductsByCategory("electronics");
+
+
+
+

Resulting HTTP Request:

+
+
+
+
GET /products?category=electronics
+
+
+
+

Why Use @QueryParam?

+
+
    +
  1. +

    Useful for filtering results (?category=electronics).

    +
  2. +
  3. +

    Ideal for pagination (?page=2&size=20).

    +
  4. +
  5. +

    Allows sending optional parameters without modifying the URL structure.

    +
  6. +
+
+
+
+
+

Using Header Parameters (@HeaderParam)

+
+

Header parameters are typically used for authentication, authorization, and metadata transmission between client and server.

+
+
+

Example:

+
+
+
+
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.Path;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+@Path("/orders")
+public interface OrderServiceClient {
+
+    @GET
+    List<Order> getOrders(@HeaderParam("Authorization") String authToken);
+}
+
+
+
+

Example Call:

+
+
+
+
orderServiceClient.getOrders("Bearer my-secret-token");
+
+
+
+

Resulting HTTP Request:

+
+
+
+
GET /orders
+Authorization: Bearer my-secret-token
+
+
+
+

Why Use @HeaderParam?

+
+
    +
  1. +

    Used for passing authentication tokens (Authorization: Bearer token).

    +
  2. +
  3. +

    Helps with custom metadata exchange (e.g., X-Correlation-ID: 12345).

    +
  4. +
  5. +

    Avoids exposing sensitive data in URLs (e.g., API keys).

    +
  6. +
+
+
+
+
+

Overview of Additional Annotations

+
+
    +
  1. +

    @CookieParam - Binds a method parameter to the value of an HTTP cookie in the incoming request.

    +
  2. +
  3. +

    @FormParam — Maps a method parameter to a field in a submitted HTML form (application/x-www-form-urlencoded POST body).

    +
  4. +
  5. +

    @MatrixParam — Binds a method parameter to a matrix parameter embedded within the URL path segements (e.g., /product;color=blue;size=large).

    +
  6. +
  7. +

    @BeanParam — Aggregates multiple parameter annotations (path, query, header, etc.) into a single Java bean for cleaner method signature.

    +
  8. +
+
+
+
+
+

Tip: These annotations eliminate manual string concatenation, making REST client calls type-safe and maintainable.

+
+
+
+
+
+
+
+

Handling Requests and Responses

+
+
+

In MicroProfile Rest Client, handling requests and responses involves defining methods in your interface that map to RESTful service endpoints. This ensures that:

+
+
+
    +
  1. +

    HTTP requests are automatically constructed based on method definitions.

    +
  2. +
  3. +

    Responses are efficiently deserialized into Java objects (DTOs) or processed manually using Response.

    +
  4. +
+
+
+

Using Jakarta RESTful Web Services annotations, you can define standard HTTP operations such as @GET, @POST, @PUT, and @DELETE. The framework also supports additional methods like @HEAD, @OPTIONS, and @PATCH, providing complete control over HTTP communication when needed. Meanwhile, MicroProfile automatically handles serialization, deserialization, and request execution at runtime.

+
+
+
+
+

Handling JSON Data formats

+
+
+

By default, MicroProfile Rest Client supports JSON format without requiring additional configurations. Serialization and deserialization of request and response bodies are automatically handled using JSON-B (Jakarta JSON Binding) or JSON-P (Jakarta JSON Processing).

+
+
+

Developers can directly use Java objects as request bodies or response entities, eliminating the need for manual parsing.

+
+
+

Example:

+
+
+
+
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.Consumes;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+@Path("/products")
+@Produces("application/json")
+@Consumes("application/json")
+public interface ProductServiceClient {
+
+    @GET
+    @Path("/{id}")
+    Product getProductById(@PathParam("id") Long id);
+}
+
+
+
+

Explanation

+
+
+
    +
  1. +

    The @Produces("application/json") annotation specifies that the client expects JSON responses. This determines the value of the Accept header in HTTP requests.

    +
  2. +
  3. +

    The @Consumes("application/json") annotation specifies that the client sends JSON requests. This determines the value of the Content-Type header of the request.

    +
  4. +
  5. +

    By default the media type "application/json" is used if @Produces and @Consumes are not explicitly set.

    +
  6. +
  7. +

    MicroProfile Rest Client automatically serializes Java objects to JSON and deserializes responses into Product DTO (Data Transfer Object) Java object.

    +
  8. +
+
+
+

Error Handling

+
+

Effective error handling is crucial when consuming remote RESTful services. MicroProfile Rest Client provides a structured approach to error handling by mapping HTTP responses to exceptions using the ResponseExceptionMapper interface.

+
+
+

This mechanism allows developers to:

+
+
+
    +
  1. +

    Convert specific HTTP response codes into custom exceptions.

    +
  2. +
  3. +

    Customize exception handling behavior at runtime.

    +
  4. +
  5. +

    Automatically throw mapped exceptions in client invocations.

    +
  6. +
+
+
+

Using ResponseExceptionMapper interface

+
+

The ResponseExceptionMapper interface allows mapping an HTTP Response object to a Throwable (custom exception). This improves error handling by ensuring meaningful exceptions are thrown instead of manually checking response codes.

+
+
+

How it Works

+
+
+
    +
  1. +

    Scanning and Prioritizing Exception Mappers: When a client method is invoked, the runtime scans all registered ResponseExceptionMapper implementations. Mappers are then sorted in ascending order of priority, determined by the @Priority annotation. The mapper with the lowest numeric priority value is checked first.

    +
  2. +
  3. +

    Handling Responses: The handles(int status, MultivaluedMap<String,Object> headers) method determines whether a mapper should handle a given response. By default, it handles responses with status code 400 or higher, but we can override this behavior.

    +
  4. +
  5. +

    Converting the Response to an Exception: The toThrowable(Response response) method converts a response into a Throwable (exception). Checked exceptions are only thrown if the client method declares that it throws that type of exception of its superclass. Unchecked exceptions (RuntimeException) are always thrown.

    +
  6. +
+
+
+

Example:

+
+
+
+
package io.microprofile.tutorial.inventory.client;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+
+@RegisterRestClient(configKey = "product-service")
+@RegisterProvider(ProductServiceResponseExceptionMapper.class)
+@Path("/products")
+public interface ProductServiceClient extends AutoCloseable {
+
+    @GET
+    @Path("/{id}")
+    Response getProductById(@PathParam("id") Long id);
+}
+
+
+
+

Explanation:

+
+
+
    +
  1. +

    The REST client interface defines an endpoint for retrieving products.

    +
  2. +
  3. +

    The @RegisterProvider annotation registers ProductServiceResponseExceptionMapper, ensuring custom exception handling.

    +
  4. +
+
+
+

And below is the corresponding ResponseExceptionMapper:

+
+
+
+
package io.microprofile.tutorial.inventory.client;
+
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import io.microprofile.tutorial.inventory.dto.ProductNotFoundException;
+
+public class ProductServiceResponseExceptionMapper implements ResponseExceptionMapper<Throwable> {
+
+    @Override
+    public Throwable toThrowable(Response response) {
+        if (response.getStatus() == 404) {
+            return new ProductNotFoundException("Product not found");
+        }
+        return new Exception("An unexpected error occurred");
+    }
+}
+
+
+
+

Explanation:

+
+
+

If the response status code is 404, a ProductNotFoundException is thrown. Otherwise, a generic exception is returned.

+
+
+
+
+

Using the RestClientBuilder Class

+
+

While CDI-based injection is commonly used for REST clients in MicroProfile, programmatic creation using the RestClientBuilder class is beneficial when CDI is unavailable or when dynamic client instantiation is required. This builder provides a fluent API for configuring and constructing REST client proxies without relying on constructors that require numerous arguments.

+
+
+

Using RestClientBuilder simplifies object creation, improves code readability, and supports method chaining, where each configuration method returns the builder instance itself.

+
+
+

Example: Inventory Service Calls Product Service

+
+

In the MicroProfile Ecommerce Store, the InventoryService must verify whether a product exists before checking or updating inventory. This interaction can be handled by calling the ProductService using a REST client interface.

+
+
+
+
package io.microprofile.tutorial.store.inventory.service;
+
+import io.microprofile.tutorial.store.inventory.client.ProductServiceClient;
+import io.microprofile.tutorial.store.product.entity.Product;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+public class InventoryService {
+
+    public boolean isProductAvailable(Long productId) {
+        URI productApiUri = URI.create("http://localhost:8080/api");
+
+        try (ProductServiceClient productClient = RestClientBuilder.newBuilder()
+                .baseUri(productApiUri)
+                .connectTimeout(3, TimeUnit.SECONDS)
+                .readTimeout(5, TimeUnit.SECONDS)
+                .build(ProductServiceClient.class)) {
+
+            Product product = productClient.getProductById(productId);
+            return product != null;
+
+        } catch (Exception e) {
+            // Log exception (omitted for brevity)
+            return false;
+        }
+    }
+}
+
+
+
+
+

Explanation

+
+
    +
  • +

    The isProductAvailable() method accepts a product ID and returns true if the product exists in the catalog.

    +
  • +
  • +

    A URI object is created pointing to the base path of the ProductService API using URI.create().

    +
  • +
  • +

    A ProductServiceClient instance is created using the builder pattern inside a try-with-resource block:

    +
    +
      +
    • +

      newBuilder() initializes the client builder.

      +
    • +
    • +

      baseUri() sets the root endpoint of the target service.

      +
    • +
    • +

      connectTimeout() and readTimeout() define connection and read timeouts respectively.

      +
    • +
    • +

      build() finalizes and returns the configured client proxy.

      +
    • +
    +
    +
  • +
  • +

    Because ProductServiceClient extends AutoCloseable, the try-with-resources block ensures that the client is automatically closed after the operation, preventing resource leaks.

    +
  • +
  • +

    If a Product object is successfully returned, true is returned.

    +
  • +
  • +

    Any exceptions are caught and handled appropriately, returning false in case of failure.

    +
  • +
+
+
+

This approach is especially useful for utility services, batch jobs, or environments where REST client configuration must be dynamic or conditional, and manual client lifecycle management is necessary.

+
+
+
+
+

Tip: When building MicroProfile REST clients programmatically (using RestClientBuilder), ensure that your client interface extends AutoCloseable and uses try-with-resources to release resources automatically.

+
+
+
+
+
+
+

Conclusion

+
+

The MicroProfile Rest Client provides a declarative, type-safe, and efficient mechanism for interacting with RESTful services in Java microservices. It reduces boilerplate code and lets developers focus on core business logic while still offering fine-grained control through features like RestClientBuilder.

+
+
+

By integrating seamlessly with other MicroProfile specifications—such as Config, Fault Tolerance, and JWT Authentication—the Rest Client helps enhance the security, resilience, and maintainability of cloud-native applications.

+
+
+

Key Takeaways

+
+
    +
  • +

    Removes boilerplate HTTP code, improving clarity and maintainability.

    +
  • +
  • +

    Automatically handles JSON serialization and deserialization.

    +
  • +
  • +

    Supports CDI injection for managed client lifecycles.

    +
  • +
  • +

    Integrates with Fault Tolerance for retries, timeouts, and circuit breakers.

    +
  • +
  • +

    Enhances security through header propagation and authentication mechanisms.

    +
  • +
+
+
+

With MicroProfile Rest Client, building robust and maintainable microservices that communicate over REST becomes simpler, more flexible, and more powerful. This concludes the MicroProfile tutorial. You are now equipped with the foundational knowledge to build robust, cloud-native microservices using the MicroProfile specification. Thank you for following along, and happy coding!

+
+
+
+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/index.html b/build/site/microprofile-tutorial/6.1/index.html new file mode 100644 index 00000000..50d19a39 --- /dev/null +++ b/build/site/microprofile-tutorial/6.1/index.html @@ -0,0 +1,437 @@ + + + + + +MicroProfile API Tutorial :: MicroProfile Tutorial :: MicroProfile Tutorial + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+

MicroProfile API Tutorial

+
+
+
+
+
MicroProfile API Tutorial
+
+Version: 6.1
+
+Status: Draft
+
+
+
+
+
+ +
+
+

Copyright (c) 2024 Contributors to the Eclipse Foundation

+
+
+

See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership.

+
+
+

Licensed under the Apache License, Version 2.0 (the "License"); +You may not use this file except in compliance with the License. +You may obtain a copy of the License at

+
+
+
+
     http://www.apache.org/licenses/LICENSE-2.0
+
+
+
+

Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.

+
+
+

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.

+
+
+
+
+

Preface

+
+
+

About this Tutorial

+
+

In this tutorial, you will learn how to use the features of the MicroProfile Platform by building a microservices-based e-commerce application named "MicroProfile e-Commerce". The tutorial will cover using MicroProfile APIs such as Config, REST Client, JWT, Fault Tolerance, and Metrics to build efficient, scalable, and resilient microservices for cloud-native applications. We aim to provide a comprehensive overview and hands-on knowledge about using MicroProfile APIs.

+
+
+
+

Who is this Tutorial for

+
+

This tutorial caters to software professionals, from beginners to senior developers, engineering managers, and architects, to adeptly utilize MicroProfile in real-world projects.

+
+
+
+

What will be Covered

+
+

First, an overview of the MicroProfile project is presented, followed by detailed sections on each specification, complete with thoroughly tested and updated code samples.

+
+
+
+

Project

+
+

In this tutorial, you will learn to build a microservices-based e-commerce application called "MicroProfile e-Commerce". The app will demonstrate the use of MicroProfile APIs for developing an application based on microservices and cloud-native architecture.

+
+
+

It would include multiple microservices, each serving a different purpose and highlighting different aspects of MicroProfile. Java developers can use this adaptation as a practical case study to implement MicroProfile APIs in real-world applications.

+
+
+

The MicroProfile e-Commerce application compirses of multiple microservices, among the key ones are as below:

+
+
+
    +
  • +

    Product Catalog: This service acts as the central repository for all product-related information, including detailed descriptions, and pricing, and inventory levels. It provides APIs for fetching product details efficiently for the other microservices, such as the Shopping Cart. This microservice is vital for updating product data, ensuring data consistency and accuracy across the e-Commerce platform.

    +
  • +
  • +

    The Shopping Cart: This service allows users to add or remove products from their shopping cart. It communicates with the Product Catalog Microservice to access up-to-date product information. It handles the storage and management of cart items for each user, including the calculation of cart totals with applicable discounts or promotions. This microservice plays interfaces with the Checkout microservice to initiate the order processing.

    +
  • +
  • +

    User Management: This service is responsible for user account management, handles registration, login, and account updates securely using JWT tokens. It is essential for personalizing the user experience and safeguarding user information.

    +
  • +
  • +

    Order Processing: This service manages the entire order process, from collecting shipping information and confirming order details to initiating payment processing. This microservice ensures a seamless transition from shopping to order completion.

    +
  • +
  • +

    Payment: Dedicated to processing payments, this microservice interacts with external payment gateways to securely handle transactions. It receives payment instructions from the Checkout microservice, executes the payment process, and confirms transaction outcomes. This microservice is crucial for ensuring financial transactions are conducted securely and efficiently, maintaining the integrity of the payment process.

    +
  • +
  • +

    Inventory: This service is dedicated to monitoring and managing inventory levels. It tracks product availability, updates inventory in real-time as sales occu, and provides restocking alerts. By integrating with the Product Catalog and Checkout microservices, it ensures that product availability is accurately reflected on the platform and that orders are only placed for in-stock items. This microservice is crucial for maintaining optimal inventory levels and preventing stockouts, thereby enhancing the customer shopping experience.

    +
  • +
  • +

    Shipping: This microservice is responsible for managing the logistics of order delivery. It receives order details and shipping information from the Order Processing Microservice, ensuring that orders are shipped to customers in a timely and efficient manner. The Shipping Microservice plays a critical role in the post-purchase customer experience, managing expectations and communication regarding order delivery.

    +
  • +
+
+
+
+MicroProfile e-Commerce Application +
+
Figure 1. MicroProfile e-Commerce Application
+
+
+

As you can see in the above figure, together these microservices form a robust and flexible e-Commerce application architecture, enabling scalable, efficient, and secure online shopping experiences.

+
+
+
+

Downloading the Code

+
+

The code examples in this tutorial are available at this repo.

+
+
+
+

Prerequisites

+
+

MicroProfile uses the Java Platform, and are usually written in the Java programming language. +All the examples in this tutorial are written in Java. +If you’re new to Java, spend some time getting up to speed on the language and platform; +a good place to start is dev.java/learn.

+
+
+

Each topic in this tutorial provides some background information, +but in general, +we assume you have a basic understanding of RESTful Web Services.

+
+
+
+

Learning Objectives

+
+
    +
  • +

    Understanding MicroProfile and Its Ecosystem:

    +
    +
      +
    • +

      Gain a solid understanding of what MicroProfile is and its role in modern cloud-native application development.

      +
    • +
    • +

      Learn about the evolution of MicroProfile and its relationship with Jakarta EE.

      +
    • +
    • +

      Understand how MicroProfile facilitates building microservices.

      +
    • +
    +
    +
  • +
  • +

    Hands-On Experience with Key MicroProfile APIs:

    +
    +
      +
    • +

      Learn to implement Config, Health, Metrics, JWT Authentication, Fault Tolerance, Rest Client, and other MicroProfile APIs.

      +
    • +
    • +

      Understand how to apply these APIs in practical scenarios through the Duke’s Forest application case study.

      +
    • +
    +
    +
  • +
  • +

    Building Resilient and Scalable Services:

    +
    +
      +
    • +

      Master techniques for developing resilient services using fault tolerance and health checks.

      +
    • +
    +
    +
  • +
  • +

    Securing Microservices:

    +
    +
      +
    • +

      Learn the intricacies of securing microservices using MicroProfile JWT and Security API.

      +
    • +
    +
    +
  • +
  • +

    Effective Data Management in Microservices:

    +
    +
      +
    • +

      Understand the role of JPA and JSON-B in MicroProfile for handling data operations in microservices.

      +
    • +
    +
    +
  • +
  • +

    Monitoring and Tracing:

    +
    +
      +
    • +

      Implement monitoring strategies using MicroProfile Metrics.

      +
    • +
    • +

      Learn to trace microservice interactions with OpenTracing for enhanced observability.

      +
    • +
    +
    +
  • +
  • +

    Collaborative Learning and Community Engagement:

    +
    +
      +
    • +

      Participate in Q&A sessions, forums, and interactive discussions.

      +
    • +
    • +

      Engage with the MicroProfile community for continuous learning and staying updated with the latest trends.

      +
    • +
    +
    +
  • +
+
+
+

By the end of this tutorial readers will gain the knowledge and skills necessary to design, develop, and deploy robust microservices using MicroProfile, preparing them for advanced roles in software development and architecture in cloud-native environments.

+
+
+
+
+
+

Conventions

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
ConventionMeaningExample

Boldface

Boldface type indicates a term defined in text or graphical user interface elements associated with an action.

A cache is a copy stored locally.

+

From the File menu, choose Open Project.

Monospace

Monospace type indicates the names of files and directories, commands within a paragraph, URLs, code in examples, text that appears on the screen, or text that you enter.

Edit your .login file.

+

Use ls -a to list all files.

+

machine_name% you have mail.

Italic

Italic type indicates book titles, emphasis, or placeholder variables for which you supply particular values.

Read Chapter 6 in the User’s Guide.

+

Do not save the file.

+

The command to remove a file is rm filename.

+
+
+ +
+
+
+
+
+

This page was built using the Antora default UI.

+

The source code for this UI is licensed under the terms of the MPL-2.0 license.

+
+ + + + + + + diff --git a/build/site/microprofile-tutorial/6.1/microprofile-tutorial.pdf b/build/site/microprofile-tutorial/6.1/microprofile-tutorial.pdf new file mode 100644 index 00000000..43e6202c Binary files /dev/null and b/build/site/microprofile-tutorial/6.1/microprofile-tutorial.pdf differ diff --git a/build/site/search-index.js b/build/site/search-index.js new file mode 100644 index 00000000..cf0eb164 --- /dev/null +++ b/build/site/search-index.js @@ -0,0 +1 @@ +antoraSearch.initSearch(lunr, {"index":{"version":"2.3.9","fields":["title","name","text","component","keyword"],"fieldVectors":[["title/1-1",[0,47.789]],["name/1-1",[]],["text/1-1",[]],["component/1-1",[]],["keyword/1-1",[]],["title/1-2",[1,28.168,2,43.396,3,37.203]],["name/1-2",[]],["text/1-2",[]],["component/1-2",[]],["keyword/1-2",[]],["title/1",[4,68.515]],["name/1",[4,0.782]],["text/1",[2,8.025,3,4.454,5,5.88,6,3.612,7,5.167,8,5.361,9,3.396,10,5.077,11,2.202,12,3.733,13,4.004,14,8.466,15,4.961,16,6.476,17,4.767,18,4.454,19,3.831,20,5.118,21,3.486,22,3.428,23,6.476,24,3.486,25,4.454,26,6.476,27,4.324,28,3.831,29,4.102,30,6.476,31,6.476,32,6.476,33,6.476,34,6.476,35,5.88,36,3.547,37,5.195,38,5.488,39,6.476,40,4.767]],["component/1",[11,0.17,12,0.288]],["keyword/1",[]],["title/2-1",[41,75.453]],["name/2-1",[]],["text/2-1",[]],["component/2-1",[]],["keyword/2-1",[]],["title/2-2",[42,75.453]],["name/2-2",[]],["text/2-2",[]],["component/2-2",[]],["keyword/2-2",[]],["title/2-3",[12,33.268]],["name/2-3",[]],["text/2-3",[]],["component/2-3",[]],["keyword/2-3",[]],["title/2-4",[12,33.268]],["name/2-4",[]],["text/2-4",[]],["component/2-4",[]],["keyword/2-4",[]],["title/2-5",[43,42.087]],["name/2-5",[]],["text/2-5",[]],["component/2-5",[]],["keyword/2-5",[]],["title/2-6",[44,39.938]],["name/2-6",[]],["text/2-6",[]],["component/2-6",[]],["keyword/2-6",[]],["title/2-7",[45,53.402,46,33.92]],["name/2-7",[]],["text/2-7",[]],["component/2-7",[]],["keyword/2-7",[]],["title/2-8",[47,75.453]],["name/2-8",[]],["text/2-8",[]],["component/2-8",[]],["keyword/2-8",[]],["title/2-9",[48,39.909,49,38.09]],["name/2-9",[]],["text/2-9",[]],["component/2-9",[]],["keyword/2-9",[]],["title/2-10",[50,55.538]],["name/2-10",[]],["text/2-10",[]],["component/2-10",[]],["keyword/2-10",[]],["title/2",[11,14.066,12,23.85,51,27.725]],["name/2",[52,0.729]],["text/2",[0,1.456,3,0.873,5,1.152,8,3.11,9,2.644,10,2.413,11,2.362,12,2.75,19,3.238,21,3.164,22,1.217,25,0.873,36,0.695,43,0.708,44,1.668,46,1.697,48,3.723,50,0.934,51,3.362,53,1.494,54,1.018,55,0.767,56,1.269,57,2.299,58,1.269,59,1.018,60,1.152,61,0.873,62,0.824,63,2.168,64,1.269,65,1.269,66,2.048,67,0.751,68,0.708,69,3.11,70,2.087,71,1.269,72,5.338,73,2.239,74,0.824,75,2.087,76,0.847,77,1.018,78,1.152,79,2.087,80,1.269,81,1.075,82,0.695,83,3.643,84,1.269,85,1.269,86,0.901,87,2.415,88,1.269,89,0.735,90,1.269,91,0.972,92,1.152,93,1.269,94,1.269,95,1.095,96,2.32,97,1.269,98,0.972,99,0.972,100,1.844,101,2.529,102,0.873,103,2.299,104,1.152,105,1.269,106,1.905,107,1.075,108,1.269,109,0.695,110,2.772,111,4.513,112,1.668,113,4.043,114,4.327,115,1.282,116,1.332,117,1.591,118,1.217,119,2.048,120,1.668,121,1.668,122,1.668,123,2.652,124,1.949,125,2.048,126,2.393,127,2.451,128,0.873,129,2.12,130,0.824,131,1.948,132,1.692,133,1.152,134,1.269,135,1.152,136,1.269,137,2.572,138,1.018,139,3.302,140,1.269,141,1.269,142,0.824,143,2.239,144,1.948,145,0.901,146,1.018,147,0.695,148,2.598,149,0.847,150,2.339,151,1.535,152,1.075,153,0.721,154,2.772,155,0.873,156,0.721,157,1.075,158,0.934,159,1.827,160,1.726,161,1.535,162,1.259,163,0.901,164,0.934,165,0.873,166,0.934,167,1.16,168,1.421,169,2.299,170,1.523,171,1.269,172,1.143,173,0.804,174,0.695,175,3.362,176,2.048,177,2.84,178,1.018,179,1.018,180,0.824,181,0.784,182,0.784,183,0.901,184,3.184,185,2.32,186,0.847,187,5.011,188,3.799,189,1.152,190,2.015,191,2.5,192,0.824,193,1.269,194,0.721,195,0.721,196,1.692,197,2.339,198,0.661,199,1.282,200,0.972,201,2.12,202,0.934,203,1.844,204,1.269,205,0.972,206,1.269,207,0.972,208,1.948,209,1.307,210,3.152,211,1.39,212,5.04,213,1.307,214,2.299,215,1.269,216,2.087,217,2.946,218,0.721,219,0.784,220,0.972,221,3.083,222,1.152,223,0.847,224,0.934,225,4.07,226,1.844,227,3.887,228,0.824,229,0.972,230,1.948,231,2.048,232,0.751,233,1.018,234,2.168,235,1.761,236,0.934,237,0.735,238,0.972,239,1.494,240,1.269,241,1.269,242,1.39,243,1.949,244,0.873,245,1.949,246,1.905,247,1.269,248,1.269,249,1.269,250,1.018,251,1.075,252,1.075,253,1.692,254,1.269,255,0.824,256,0.804,257,1.269,258,1.075,259,1.39,260,1.726,261,1.269,262,2.299,263,1.018,264,0.784,265,2.393,266,0.934,267,1.269,268,0.824,269,2.087,270,0.901,271,0.824,272,0.972,273,0.972,274,1.421,275,0.804,276,0.721,277,1.269,278,2.199,279,1.018,280,2.087,281,0.873,282,1.018,283,0.751,284,1.269,285,0.873,286,1.018,287,1.269,288,0.735,289,1.269,290,0.735,291,1.269,292,0.683,293,1.018,294,0.847,295,2.745,296,0.683,297,1.018,298,1.761,299,1.152,300,0.767,301,1.152,302,1.018,303,0.604,304,0.751,305,0.847,306,1.197,307,0.751,308,0.972,309,0.901,310,0.767,311,1.269,312,1.269,313,1.152,314,1.075,315,0.613,316,0.972,317,0.804,318,1.018,319,0.695,320,1.018,321,0.735,322,1.178,323,0.784,324,1.269,325,0.972,326,1.018,327,2.299,328,1.152,329,1.269,330,1.018,331,1.269,332,0.972,333,0.901,334,1.152,335,1.018,336,1.075,337,0.873,338,1.152,339,1.075,340,0.708,341,0.767,342,0.721,343,1.018,344,0.934,345,0.64,346,0.824,347,2.299,348,1.615,349,2.048,350,1.075,351,0.695,352,2.862,353,1.269,354,1.269,355,0.934,356,1.018,357,1.018,358,0.784,359,1.269,360,0.901,361,2.299,362,0.934,363,1.494,364,0.767,365,1.269,366,0.847,367,1.269,368,1.269,369,0.972,370,1.018,371,1.269,372,0.735,373,1.269,374,1.269,375,2.299,376,1.269,377,1.152,378,1.269,379,0.972,380,0.972,381,1.269,382,1.075,383,0.824,384,0.934,385,0.751,386,1.269,387,1.152,388,0.873,389,1.269,390,1.269,391,1.269]],["component/2",[11,0.17,12,0.288]],["keyword/2",[]],["title/3-1",[392,40.617]],["name/3-1",[]],["text/3-1",[]],["component/3-1",[]],["keyword/3-1",[]],["title/3-2",[43,35.147,290,36.524]],["name/3-2",[]],["text/3-2",[]],["component/3-2",[]],["keyword/3-2",[]],["title/3-3",[11,19.621]],["name/3-3",[]],["text/3-3",[]],["component/3-3",[]],["keyword/3-3",[]],["title/3-4",[11,16.386,393,33.92]],["name/3-4",[]],["text/3-4",[]],["component/3-4",[]],["keyword/3-4",[]],["title/3-5",[11,16.386,95,30.021]],["name/3-5",[]],["text/3-5",[]],["component/3-5",[]],["keyword/3-5",[]],["title/3-6",[11,12.322,95,22.576,110,29.301,394,28.035]],["name/3-6",[]],["text/3-6",[]],["component/3-6",[]],["keyword/3-6",[]],["title/3-7",[95,22.576,395,40.158,396,47.385,397,47.385]],["name/3-7",[]],["text/3-7",[]],["component/3-7",[]],["keyword/3-7",[]],["title/3-8",[11,16.386,170,30.441]],["name/3-8",[]],["text/3-8",[]],["component/3-8",[]],["keyword/3-8",[]],["title/3-9",[159,36.524,398,57.218]],["name/3-9",[]],["text/3-9",[]],["component/3-9",[]],["keyword/3-9",[]],["title/3-10",[11,16.386,399,42.076]],["name/3-10",[]],["text/3-10",[]],["component/3-10",[]],["keyword/3-10",[]],["title/3-11",[95,22.576,302,38.015,303,22.576,304,28.035]],["name/3-11",[]],["text/3-11",[]],["component/3-11",[]],["keyword/3-11",[]],["title/3-12",[400,55.538]],["name/3-12",[]],["text/3-12",[]],["component/3-12",[]],["keyword/3-12",[]],["title/3-13",[401,63.946]],["name/3-13",[]],["text/3-13",[]],["component/3-13",[]],["keyword/3-13",[]],["title/3",[11,16.386,392,33.92]],["name/3",[402,0.861]],["text/3",[0,0.322,1,1.314,6,0.283,7,1.193,9,1.735,10,2.486,11,2.285,12,0.621,13,0.603,17,0.374,18,0.671,19,1.493,20,0.59,21,2.125,22,0.957,24,0.525,27,0.94,28,0.301,36,2.53,44,0.516,46,1.359,48,0.322,49,0.307,51,3.059,55,0.59,59,0.408,60,0.461,61,1.243,62,1.415,66,1.64,67,0.577,68,0.283,69,0.289,74,0.33,82,0.535,83,4.078,87,1.078,89,1.263,95,3.428,99,0.748,100,0.408,101,3.84,109,1.383,110,3.775,111,4.266,112,2.333,115,1.408,116,0.295,117,1.732,118,0.957,119,0.516,120,1.817,121,1.817,122,1.336,123,1.493,124,1.947,125,2.719,126,4.006,127,3.498,128,0.671,129,3.269,130,0.634,133,0.461,137,3.932,139,2.215,143,0.361,144,0.431,145,0.693,147,0.771,148,0.295,149,0.339,150,1.526,153,0.555,158,0.374,159,3.106,160,1.725,161,0.652,162,0.99,166,1.33,167,0.912,170,2.021,172,0.485,173,0.618,174,0.771,175,1.294,177,3.675,182,1.118,183,0.361,190,2.179,191,2.334,192,1.64,194,2.102,197,3.027,198,0.733,199,0.283,201,0.771,207,1.078,208,0.431,209,0.289,211,0.851,213,1.618,217,1.359,219,1.947,224,1.036,228,0.33,229,0.748,231,0.634,232,1.069,234,0.35,237,0.295,239,0.914,242,0.851,243,1.561,244,0.671,245,0.603,246,0.307,253,0.718,255,1.64,256,0.322,258,0.431,259,2.075,260,0.535,263,0.408,264,0.87,265,1.347,269,0.886,270,0.361,275,0.618,276,1.951,281,2.543,283,1.289,286,0.783,288,0.295,292,0.525,295,0.99,296,1.359,297,0.783,300,0.851,301,0.886,302,0.408,303,3.141,304,3.688,305,2.103,306,1.482,307,1.069,309,0.693,310,0.307,314,0.431,315,1.052,317,0.618,319,2.292,320,0.408,321,1.65,322,1.117,325,1.385,326,1.129,328,0.886,332,0.389,333,2.022,334,0.461,336,0.827,339,0.431,340,0.283,341,2.664,342,2.102,344,0.718,345,1.866,348,0.5,351,1.88,355,0.718,356,1.129,364,0.851,370,0.783,372,0.816,385,0.577,393,2.252,394,1.493,395,1.193,398,0.886,399,1.455,403,0.508,404,1.991,405,0.461,406,0.408,407,0.783,408,0.389,409,0.33,410,0.508,411,1.684,412,3.084,413,1.455,414,2.412,415,0.671,416,0.783,417,1.76,418,1.038,419,0.508,420,0.508,421,2.139,422,1.407,423,2.179,424,0.652,425,0.976,426,0.374,427,2.906,428,2.585,429,0.508,430,0.634,431,0.389,432,2.963,433,1.243,434,0.783,435,0.374,436,1.347,437,1.207,438,2.025,439,0.461,440,0.508,441,0.307,442,0.508,443,0.508,444,0.508,445,0.508,446,0.508,447,0.508,448,0.508,449,0.431,450,0.508,451,0.508,452,0.508,453,0.508,454,0.671,455,0.408,456,0.508,457,0.508,458,0.976,459,0.461,460,2.025,461,0.816,462,0.508,463,2.438,464,0.748,465,0.508,466,0.508,467,0.508,468,1.33,469,0.389,470,1.145,471,0.374,472,2.284,473,0.361,474,0.322,475,1.207,476,0.833,477,1.207,478,1.561,479,1.684,480,1.24,481,0.314,482,1.193,483,1.193,484,0.508,485,0.461,486,0.461,487,0.508,488,0.461,489,0.886,490,1.561,491,0.783,492,1.33,493,0.508,494,0.976,495,2.046,496,0.748,497,0.508,498,0.976,499,0.652,500,2.963,501,0.431,502,0.461,503,0.508,504,1.618,505,1.243,506,1.934,507,1.807,508,1.807,509,0.508,510,1.174,511,1.278,512,2.607,513,0.374,514,1.604,515,1.145,516,0.891,517,1.641,518,1.934,519,1.278,520,0.914,521,1.686,522,0.408,523,1.64,524,1.036,525,1.599,526,1.415,527,0.408,528,0.508,529,0.508,530,0.322,531,1.736,532,0.389,533,1.385,534,0.662,535,0.914,536,0.671,537,1.263,538,0.461,539,0.976,540,0.461,541,1.547,542,0.976,543,0.508,544,0.408,545,1.129,546,0.827,547,2.292,548,3.698,549,1,550,1.129,551,2.856,552,0.748,553,1.347,554,0.389,555,0.374,556,0.508,557,1.455,558,1.278,559,0.408,560,0.508,561,0.891,562,0.461,563,0.361,564,0.374,565,0.408,566,0.508,567,0.718,568,0.748,569,0.914,570,0.914,571,0.891,572,0.891,573,0.891,574,0.618,575,0.461,576,0.693,577,0.461,578,0.508,579,0.508,580,0.577,581,0.652,582,0.314,583,0.389,584,0.408,585,0.671,586,0.431,587,0.33,588,0.508,589,0.652,590,0.408,591,0.35,592,0.431,593,0.671,594,0.718,595,0.339,596,0.322,597,0.508,598,0.652,599,0.35,600,0.431,601,0.535,602,0.783,603,0.508,604,0.374,605,0.508,606,0.461,607,0.508,608,0.94,609,1.38,610,0.389,611,0.508,612,0.361,613,0.94,614,0.374,615,0.976,616,0.374,617,0.461,618,1,619,0.99,620,0.508,621,0.508,622,0.94,623,0.508,624,1.078,625,0.603,626,0.671,627,0.431,628,0.35,629,0.374,630,0.33,631,0.508,632,0.339,633,0.35,634,0.374,635,0.408,636,0.827,637,0.968,638,0.461,639,1.978,640,0.783,641,0.33,642,0.461,643,0.718,644,0.35,645,0.976,646,0.603,647,0.508,648,0.891,649,0.431,650,0.35,651,0.693,652,0.976,653,1.078,654,0.693,655,0.431,656,0.431,657,0.408,658,0.508,659,1.129,660,0.508,661,0.374,662,0.339,663,0.461,664,0.389,665,0.59,666,0.671,667,0.265,668,0.461,669,0.94,670,0.361,671,0.976,672,0.976,673,0.35,674,0.886,675,0.431,676,0.361,677,0.508,678,0.652,679,0.389,680,0.748,681,0.508,682,0.718,683,0.408,684,0.508,685,1,686,0.652,687,0.508,688,0.783,689,0.431,690,0.461,691,0.718,692,0.431,693,0.374,694,0.652,695,0.389,696,0.508,697,0.339,698,0.508,699,0.461,700,0.461,701,0.461,702,0.461,703,0.652,704,0.508,705,0.408,706,0.566,707,0.461,708,0.508,709,0.461,710,0.508,711,0.886,712,0.508,713,0.431,714,0.748,715,0.431,716,0.431,717,0.389,718,0.461,719,0.35,720,0.431,721,0.307,722,0.339,723,0.508,724,0.431,725,0.408,726,1.129,727,0.408,728,0.322,729,0.389,730,0.374,731,0.671,732,0.339,733,1.561,734,0.389,735,1.036,736,0.361,737,0.408,738,0.408,739,0.408,740,0.431,741,0.33,742,1.193,743,0.408,744,0.408,745,0.431,746,0.431,747,0.314,748,0.431,749,0.431,750,0.431,751,0.431,752,0.26,753,0.886,754,1.048,755,0.33,756,0.389,757,0.461,758,0.408,759,0.508,760,0.508,761,0.508,762,0.431,763,0.322,764,0.339,765,0.431,766,0.431,767,0.461,768,0.33,769,0.461,770,0.389,771,0.431,772,0.389,773,0.461,774,0.389,775,1.036,776,0.652,777,0.461,778,0.508,779,0.339,780,0.431,781,0.408,782,0.431,783,0.408,784,0.461,785,0.431,786,0.461,787,0.508,788,0.374,789,0.389,790,0.408,791,0.361,792,0.339,793,0.508,794,0.389,795,0.431,796,0.461,797,0.508,798,0.783,799,0.361,800,0.408,801,0.508]],["component/3",[11,0.17,12,0.288]],["keyword/3",[]],["title/4-1",[392,40.617]],["name/4-1",[]],["text/4-1",[]],["component/4-1",[]],["keyword/4-1",[]],["title/4-2",[43,35.147,290,36.524]],["name/4-2",[]],["text/4-2",[]],["component/4-2",[]],["keyword/4-2",[]],["title/4-3",[137,25.423,345,27.302,802,34.26]],["name/4-3",[]],["text/4-3",[]],["component/4-3",[]],["keyword/4-3",[]],["title/4-4",[101,23.916,137,22.271,803,36.303,804,36.303]],["name/4-4",[]],["text/4-4",[]],["component/4-4",[]],["keyword/4-4",[]],["title/4-5",[10,25.508,24,25.508,805,25.081,806,28.035]],["name/4-5",[]],["text/4-5",[]],["component/4-5",[]],["keyword/4-5",[]],["title/4-6",[74,35.146,805,28.631,807,43.396]],["name/4-6",[]],["text/4-6",[]],["component/4-6",[]],["keyword/4-6",[]],["title/4-7",[806,44.641]],["name/4-7",[]],["text/4-7",[]],["component/4-7",[]],["keyword/4-7",[]],["title/4-8",[22,28.631,137,25.423,345,27.302]],["name/4-8",[]],["text/4-8",[]],["component/4-8",[]],["keyword/4-8",[]],["title/4-9",[61,28.995,101,21.278,137,19.814,296,22.694,412,28.151]],["name/4-9",[]],["text/4-9",[]],["component/4-9",[]],["keyword/4-9",[]],["title/4-10",[808,48.275,809,48.275]],["name/4-10",[]],["text/4-10",[]],["component/4-10",[]],["keyword/4-10",[]],["title/4-11",[74,40.941,810,48.275]],["name/4-11",[]],["text/4-11",[]],["component/4-11",[]],["keyword/4-11",[]],["title/4-12",[46,29.118,811,35.146,812,41.442]],["name/4-12",[]],["text/4-12",[]],["component/4-12",[]],["keyword/4-12",[]],["title/4-13",[11,12.322,199,26.431,404,25.508,601,25.957]],["name/4-13",[]],["text/4-13",[]],["component/4-13",[]],["keyword/4-13",[]],["title/4-14",[36,34.517,630,40.941]],["name/4-14",[]],["text/4-14",[]],["component/4-14",[]],["keyword/4-14",[]],["title/4-15",[632,50.384]],["name/4-15",[]],["text/4-15",[]],["component/4-15",[]],["keyword/4-15",[]],["title/4-16",[454,43.338,628,43.338]],["name/4-16",[]],["text/4-16",[]],["component/4-16",[]],["keyword/4-16",[]],["title/4-17",[633,51.895]],["name/4-17",[]],["text/4-17",[]],["component/4-17",[]],["keyword/4-17",[]],["title/4-18",[634,55.538]],["name/4-18",[]],["text/4-18",[]],["component/4-18",[]],["keyword/4-18",[]],["title/4-19",[74,40.941,629,46.38]],["name/4-19",[]],["text/4-19",[]],["component/4-19",[]],["keyword/4-19",[]],["title/4-20",[11,16.386,813,50.551]],["name/4-20",[]],["text/4-20",[]],["component/4-20",[]],["keyword/4-20",[]],["title/4",[11,14.066,285,37.203,288,31.354]],["name/4",[814,0.31,815,0.451]],["text/4",[0,2.32,1,1.908,2,2.004,3,0.663,6,0.997,7,0.979,8,1.42,9,2.318,10,4.228,11,2.366,12,1.375,13,0.596,15,1.913,18,0.663,20,2.768,21,0.519,22,0.946,24,2.465,29,1.975,36,2.272,44,4.409,45,4.508,46,2.233,50,1.838,51,0.494,53,4.329,54,0.773,59,1.433,61,1.229,63,1.718,66,0.626,68,0.538,74,3.228,82,0.979,83,3.174,86,0.685,89,0.559,95,1.745,96,0.709,100,1.433,101,4.149,106,1.51,109,2.007,110,1.928,111,2.212,112,0.946,115,0.997,116,0.559,117,0.902,118,0.51,119,0.51,120,0.51,121,0.51,122,0.51,123,1.478,124,0.596,126,2.565,127,1.975,128,0.663,129,3.393,130,0.626,137,4.01,139,1.322,145,2.214,147,2.721,148,0.559,150,1.08,151,1.193,153,1.016,154,0.596,155,0.663,160,2.007,162,0.528,164,0.709,170,0.863,172,0.888,173,1.132,174,2.272,175,1.28,177,0.466,180,0.626,181,0.596,191,0.538,192,0.626,194,1.42,197,1.08,198,0.502,199,1.393,200,0.738,211,0.583,219,1.105,220,0.738,226,0.773,236,2.295,246,1.51,255,0.626,259,1.51,260,0.979,274,0.596,275,2.32,276,1.016,278,0.548,281,1.229,283,0.57,285,2.853,286,0.773,288,3.731,292,1.972,295,1.368,296,1.344,300,0.583,303,2.182,304,3.147,306,0.502,310,1.51,315,0.466,321,0.559,322,0.494,335,3.327,340,0.997,341,1.08,342,1.772,343,0.773,345,3.126,357,1.433,358,0.596,360,1.774,363,2.695,369,0.738,370,1.433,372,0.559,385,0.57,388,2.144,393,1.678,394,0.57,404,1.678,407,1.433,412,0.644,413,1.193,414,1.369,417,0.596,418,2.182,424,0.644,426,0.709,427,0.709,430,0.626,432,2.454,435,0.709,436,3.289,437,0.644,449,0.817,454,1.718,455,2.501,461,0.559,468,0.709,469,0.738,470,1.132,473,1.269,475,0.644,477,0.644,478,1.544,480,2.22,483,0.817,490,1.105,495,2.026,499,0.644,504,0.548,512,3.335,515,1.582,516,0.61,520,0.626,521,0.644,524,0.709,531,0.663,534,1.722,537,0.559,551,1.544,553,1.105,561,1.132,591,0.663,596,0.61,601,1.708,602,0.773,628,0.663,629,2.295,630,1.623,632,1.193,633,1.718,634,1.315,644,1.229,646,0.596,651,0.685,659,2.501,662,1.668,665,1.885,667,0.93,669,1.193,670,0.685,673,0.663,676,0.685,679,0.738,683,0.773,685,0.685,705,0.773,706,1.036,727,1.433,733,0.596,742,2.642,753,0.876,764,0.644,770,1.913,791,0.685,792,0.644,802,1.582,803,0.738,804,4.075,805,2.984,806,3.335,807,6.19,808,1.913,809,1.913,810,1.369,811,0.626,812,0.738,813,2.501,816,0.875,817,0.875,818,1.622,819,0.709,820,1.913,821,0.773,822,0.738,823,0.773,824,0.709,825,0.709,826,0.875,827,0.738,828,0.709,829,1.315,830,1.42,831,2.214,832,2.501,833,3.766,834,1.514,835,1.975,836,1.774,837,1.718,838,2.268,839,0.773,840,0.875,841,2.831,842,1.622,843,2.268,844,0.875,845,0.875,846,0.875,847,0.817,848,0.738,849,0.709,850,1.622,851,0.685,852,0.875,853,1.269,854,1.269,855,0.817,856,0.875,857,0.875,858,0.875,859,0.875,860,0.685,861,0.817,862,0.709,863,0.817,864,0.817,865,1.913,866,2.389,867,0.773,868,0.875,869,0.709,870,0.875,871,0.875,872,0.875,873,0.875,874,2.268,875,0.875,876,0.875,877,0.875,878,3.674,879,0.738,880,0.875,881,0.875,882,0.875,883,0.875,884,0.875,885,0.875,886,0.596,887,0.875,888,0.817,889,0.875,890,0.817,891,1.622,892,1.622,893,1.622,894,1.622,895,1.433,896,0.875,897,1.622,898,0.875,899,0.875,900,0.583,901,0.528,902,0.817,903,1.622,904,2.117,905,0.709,906,0.875,907,0.875,908,0.875,909,1.514,910,0.875,911,0.817,912,0.875,913,0.875,914,1.622,915,0.875,916,0.875,917,0.875,918,0.875,919,0.875,920,0.875,921,0.685,922,0.875,923,0.773,924,0.875,925,0.685,926,0.773,927,0.875,928,0.773,929,0.875,930,1.433,931,1.08,932,3.003,933,0.773,934,0.875,935,0.817,936,0.875,937,0.875,938,0.875,939,0.773,940,0.773,941,0.773,942,0.875,943,0.817,944,0.709,945,0.875,946,0.875,947,0.685,948,0.817,949,0.644,950,0.875,951,1.622,952,2.501,953,0.773,954,0.596,955,0.644,956,0.773,957,0.685,958,0.738,959,0.644,960,0.875,961,0.875,962,0.685,963,0.596,964,0.875,965,0.817,966,1.433,967,0.663,968,0.875,969,0.875,970,0.817,971,1.08,972,0.817,973,1.514,974,0.596,975,0.817,976,0.817,977,0.875,978,0.817,979,0.559,980,0.773,981,1.369,982,0.738,983,0.644,984,0.773,985,0.773,986,0.875,987,0.875,988,0.875,989,0.875]],["component/4",[11,0.17,12,0.288]],["keyword/4",[]],["title/5-1",[11,10.963,44,22.314,101,21.278,137,19.814,418,20.085]],["name/5-1",[]],["text/5-1",[]],["component/5-1",[]],["keyword/5-1",[]],["title/5-2",[9,22.686,932,38.09]],["name/5-2",[]],["text/5-2",[]],["component/5-2",[]],["keyword/5-2",[]],["title/5-3",[9,15.178,363,27.391,805,22.314,836,29.944,837,28.995]],["name/5-3",[]],["text/5-3",[]],["component/5-3",[]],["keyword/5-3",[]],["title/5",[]],["name/5",[814,0.31,839,0.362]],["text/5",[1,2.057,7,2.163,8,3.393,9,2.464,10,2.126,11,2.143,12,1.741,19,0.993,21,2.126,27,1.12,36,0.919,44,5.21,46,2.126,49,1.014,51,2.776,53,1.09,62,1.09,69,0.954,82,1.616,83,3.301,86,2.805,89,1.71,95,0.799,101,3.632,106,2.874,112,3.16,113,1.154,114,1.235,129,2.604,137,1.387,138,2.367,146,1.346,147,1.616,153,4.266,155,1.154,160,2.163,165,1.154,168,1.038,174,1.616,175,1.512,177,0.811,181,1.825,191,1.646,194,1.677,195,1.677,199,0.936,211,1.014,219,1.825,231,1.917,236,1.235,237,2.289,271,1.09,274,1.038,276,0.954,279,1.346,283,2.812,288,0.973,292,2.915,294,1.12,300,1.014,303,2.265,304,2.812,310,1.784,315,0.811,340,1.646,345,0.847,346,1.09,349,1.917,351,0.919,358,3.349,363,4.676,372,0.973,388,1.154,393,0.903,404,2.915,412,1.97,416,1.346,417,1.038,418,2.265,426,2.172,435,1.235,441,1.014,474,2.501,480,1.162,500,1.746,512,0.993,515,1.063,520,1.09,524,1.235,531,2.716,534,4.468,537,0.973,564,1.235,601,0.919,612,1.192,618,1.192,625,1.038,641,1.09,662,1.12,695,2.261,706,0.973,721,1.784,732,1.12,733,1.038,753,0.822,774,1.285,791,1.192,799,1.192,802,1.063,805,4.117,824,1.235,836,3.377,837,1.154,848,1.285,854,1.192,867,1.346,879,3.026,890,1.422,900,3.274,901,6.041,921,2.096,930,1.346,932,2.387,953,1.346,954,2.939,971,2.387,972,2.501,973,4.029,974,1.038,976,1.422,978,4.029,979,0.973,990,2.029,991,6.216,992,2.679,993,2.679,994,2.679,995,2.679,996,4.317,997,2.679,998,2.679,999,1.063,1000,1.524,1001,1.422,1002,2.679,1003,1.869,1004,2.679,1005,1.346,1006,1.524,1007,1.09,1008,3.347,1009,1.524,1010,1.524,1011,2.679,1012,1.038,1013,1.422,1014,1.422,1015,1.422,1016,1.524,1017,2.029,1018,1.524,1019,1.524,1020,1.154,1021,1.524,1022,1.524,1023,1.154,1024,1.154,1025,1.524,1026,1.346,1027,1.524,1028,1.524,1029,1.524,1030,1.524,1031,1.524,1032,1.524,1033,1.524,1034,1.038,1035,1.524,1036,1.422,1037,1.422,1038,1.524,1039,1.422,1040,1.422,1041,1.422,1042,1.014,1043,1.422,1044,1.422,1045,1.422,1046,1.422,1047,1.422,1048,1.422,1049,3.347,1050,1.524,1051,1.524,1052,1.524,1053,3.026,1054,1.422,1055,1.422,1056,1.869,1057,1.422,1058,1.524,1059,1.422,1060,1.422,1061,1.422,1062,1.422,1063,5.059,1064,5.421,1065,2.679,1066,2.679,1067,2.679,1068,2.679,1069,1.524,1070,1.524,1071,1.235,1072,1.346,1073,1.285,1074,1.346,1075,1.422,1076,1.524,1077,1.97,1078,1.422]],["component/5",[11,0.17,12,0.288]],["keyword/5",[]],["title/6-1",[11,10.963,83,21.608,360,29.944,822,32.298,823,33.821]],["name/6-1",[]],["text/6-1",[]],["component/6-1",[]],["keyword/6-1",[]],["title/6-2",[9,17.06,11,12.322,223,31.641,534,22.271]],["name/6-2",[]],["text/6-2",[]],["component/6-2",[]],["keyword/6-2",[]],["title/6-3",[9,15.178,11,10.963,95,20.085,424,28.151,534,19.814]],["name/6-3",[]],["text/6-3",[]],["component/6-3",[]],["keyword/6-3",[]],["title/6",[]],["name/6",[814,0.31,909,0.382]],["text/6",[1,0.853,7,0.897,9,1.92,11,2.504,18,1.127,20,0.99,22,2.051,24,1.555,28,2.293,44,2.822,51,3.016,53,1.064,54,2.318,55,0.99,66,1.877,68,0.914,69,1.642,82,3.704,83,4.61,87,1.255,89,1.674,95,3.668,101,0.827,109,0.897,111,3.07,112,1.529,115,0.914,116,1.674,117,2.359,118,1.529,119,2.051,120,2.474,121,2.474,122,2.822,123,0.969,125,1.064,126,1.013,127,1.037,128,1.987,129,2.921,137,0.77,139,0.867,147,0.897,149,1.094,156,0.931,160,3.481,162,2.123,165,1.127,167,0.827,170,0.791,173,1.037,174,0.897,175,0.84,177,2.576,182,1.013,186,1.094,190,0.853,191,2.607,194,1.642,195,0.931,196,1.206,198,0.853,217,2.086,218,0.931,219,1.013,221,1.127,223,1.929,228,1.064,232,0.969,239,1.064,242,2.343,243,2.396,245,1.013,246,1.746,255,1.064,256,1.037,264,1.013,282,2.318,283,0.969,288,2.246,292,1.555,295,1.582,303,1.846,304,2.293,306,2.776,307,1.709,315,0.791,322,1.986,340,2.162,341,0.99,342,1.642,344,1.206,345,2.359,348,1.481,351,1.582,355,1.206,360,2.753,372,0.949,388,1.127,393,4.145,399,1.094,404,2.086,415,1.127,417,1.786,424,1.094,426,1.206,436,1.786,460,2.318,461,0.949,469,1.255,470,1.037,475,1.094,476,0.969,477,1.094,478,1.786,479,0.969,480,1.526,481,1.786,490,1.013,492,1.206,495,1.064,504,0.931,513,2.126,515,1.037,523,2.518,525,2.454,526,1.064,534,4.91,537,2.709,541,2.753,544,1.314,553,2.89,554,1.255,569,1.064,570,1.064,571,1.037,572,1.037,573,1.037,574,1.037,580,1.709,587,2.518,589,1.929,594,2.126,596,1.037,598,2.588,599,1.987,600,1.388,601,1.582,602,2.318,604,1.206,608,1.094,609,1.83,619,0.897,622,1.094,637,1.127,644,2.665,665,2.825,667,0.853,669,1.094,670,1.163,676,2.052,678,2.588,682,1.206,703,2.588,706,0.949,736,1.163,747,1.013,753,0.803,754,0.949,763,1.037,764,1.094,779,1.094,788,1.206,799,2.052,805,3.579,806,0.969,820,1.255,821,1.314,822,1.255,823,4.277,861,1.388,900,0.99,901,3.224,926,1.314,931,0.99,947,1.163,952,3.749,955,1.094,957,2.052,959,1.094,975,1.388,979,0.949,983,1.929,985,1.314,1053,2.213,1054,1.388,1055,1.388,1056,4.506,1057,1.388,1059,1.388,1060,1.388,1061,1.388,1062,2.448,1079,1.487,1080,1.094,1081,1.314,1082,1.314,1083,1.487,1084,1.255,1085,1.314,1086,1.163,1087,1.388,1088,1.487,1089,1.388,1090,1.388,1091,1.388,1092,1.314,1093,1.487,1094,1.314,1095,2.213,1096,1.127,1097,1.388,1098,1.487,1099,1.064,1100,1.388,1101,1.388,1102,1.487,1103,1.206,1104,1.487,1105,1.388,1106,4.129,1107,1.314,1108,1.314,1109,1.388,1110,1.388,1111,1.388,1112,1.487,1113,1.206,1114,1.206,1115,1.388,1116,1.388,1117,1.487,1118,1.255,1119,1.388,1120,1.388,1121,1.037,1122,1.388,1123,1.388,1124,1.987,1125,1.388,1126,1.487,1127,1.314,1128,1.314,1129,2.623,1130,2.623,1131,2.623,1132,1.487,1133,1.487,1134,1.487,1135,1.487,1136,1.388,1137,1.255,1138,2.052,1139,1.206]],["component/6",[11,0.17,12,0.288]],["keyword/6",[]],["title/7-1",[117,23.916,137,22.271,177,22.891,296,25.508]],["name/7-1",[]],["text/7-1",[]],["component/7-1",[]],["keyword/7-1",[]],["title/7-2",[418,25.771,1012,33.448,1140,25.771]],["name/7-2",[]],["text/7-2",[]],["component/7-2",[]],["keyword/7-2",[]],["title/7-3",[418,25.771,753,26.506,1140,25.771]],["name/7-3",[]],["text/7-3",[]],["component/7-3",[]],["keyword/7-3",[]],["title/7-4",[418,25.771,1140,25.771,1141,49.119]],["name/7-4",[]],["text/7-4",[]],["component/7-4",[]],["keyword/7-4",[]],["title/7-5",[10,33.92,83,32.297]],["name/7-5",[]],["text/7-5",[]],["component/7-5",[]],["keyword/7-5",[]],["title/7-6",[111,30.441,342,35.814]],["name/7-6",[]],["text/7-6",[]],["component/7-6",[]],["keyword/7-6",[]],["title/7-7",[292,33.92,1142,40.941]],["name/7-7",[]],["text/7-7",[]],["component/7-7",[]],["keyword/7-7",[]],["title/7-8",[342,35.814,954,38.963]],["name/7-8",[]],["text/7-8",[]],["component/7-8",[]],["keyword/7-8",[]],["title/7-9",[1,32.812,83,32.297]],["name/7-9",[]],["text/7-9",[]],["component/7-9",[]],["keyword/7-9",[]],["title/7-10",[68,35.147,1142,40.941]],["name/7-10",[]],["text/7-10",[]],["component/7-10",[]],["keyword/7-10",[]],["title/7",[]],["name/7",[814,0.31,1143,0.451]],["text/7",[1,2.553,6,1.095,7,0.585,8,2.786,9,2.256,10,2.853,11,1.159,12,0.471,20,2.964,22,0.565,24,1.057,28,0.631,29,4.794,36,2.167,37,2.187,44,0.565,46,1.467,48,0.676,49,3.203,51,1.397,53,0.693,63,0.734,67,0.631,69,0.607,73,0.758,82,0.585,83,4.247,89,0.619,95,1.612,101,0.991,102,0.734,106,2.964,110,1.214,111,0.516,112,1.443,115,0.595,117,3.016,118,1.039,124,1.214,126,1.214,127,1.243,132,0.786,137,1.281,139,0.565,143,0.758,145,1.394,147,2.167,148,1.961,149,0.713,150,2.045,153,2.534,154,0.66,155,1.35,156,0.607,159,1.58,160,1.075,162,1.075,168,0.66,170,1.317,173,1.726,174,2.686,175,3.789,176,1.276,177,3.156,181,0.66,182,2.092,183,2.809,190,1.419,191,1.095,194,1.116,195,1.549,197,0.645,198,2.321,209,0.607,211,1.187,213,1.549,224,0.786,236,2.49,237,1.138,245,1.214,271,1.771,276,0.607,278,1.923,283,0.631,285,1.35,288,1.58,292,2.129,293,0.856,295,0.585,296,3.767,300,0.645,303,0.935,310,1.187,319,2.686,321,0.619,340,0.595,341,0.645,342,4.094,345,0.539,348,2.716,358,1.214,360,0.758,362,0.786,363,3.443,364,0.645,366,2.259,369,0.818,372,2.584,379,0.818,385,0.631,388,1.875,393,0.575,409,0.693,411,1.162,414,0.818,418,2.694,426,1.445,430,0.693,432,0.631,433,0.734,441,1.648,454,2.327,461,0.619,474,0.676,476,0.631,477,0.713,480,2.227,504,1.923,512,0.631,515,1.243,520,0.693,521,1.311,525,0.676,534,0.502,551,0.66,557,1.311,580,0.631,582,1.214,591,0.734,601,2.442,609,1.243,619,1.075,625,0.66,628,2.327,629,2.49,630,3.674,632,1.82,633,2.327,634,2.911,650,0.734,651,0.758,688,0.856,693,0.786,694,1.311,697,0.713,706,0.619,721,1.187,727,0.856,728,0.676,729,0.818,732,0.713,736,0.758,743,0.856,744,0.856,747,1.214,752,2.716,753,2.402,754,3.464,756,0.818,763,1.243,764,0.713,768,1.276,776,0.713,794,0.818,805,1.039,806,1.162,825,1.445,830,1.549,836,0.758,837,0.734,848,0.818,864,0.905,865,2.088,867,4.537,879,3.03,886,0.66,895,0.856,900,1.648,901,4.539,921,1.394,930,1.575,931,3.949,932,2.391,944,2.49,954,0.66,959,0.713,962,1.394,963,4.454,966,0.856,974,0.66,979,0.619,980,2.714,981,0.818,983,0.713,984,0.856,999,1.726,1001,5.065,1003,0.676,1007,0.693,1012,1.214,1013,0.905,1014,0.905,1015,0.905,1017,0.734,1020,0.734,1024,1.35,1026,0.856,1034,2.092,1036,0.905,1037,0.905,1039,0.905,1040,0.905,1041,0.905,1042,3.419,1043,0.905,1044,0.905,1045,0.905,1046,0.905,1047,0.905,1048,0.905,1071,0.786,1072,0.856,1073,2.088,1092,0.856,1140,3.432,1142,0.693,1144,0.969,1145,0.969,1146,0.969,1147,1.575,1148,1.445,1149,0.905,1150,0.905,1151,0.905,1152,2.446,1153,0.905,1154,0.969,1155,0.905,1156,0.905,1157,0.905,1158,1.575,1159,1.575,1160,2.391,1161,2.641,1162,1.771,1163,1.445,1164,0.856,1165,0.905,1166,0.818,1167,1.875,1168,0.969,1169,1.783,1170,0.786,1171,0.969,1172,0.758,1173,0.856,1174,0.905,1175,0.713,1176,0.818,1177,3.066,1178,1.875,1179,0.818,1180,0.969,1181,0.818,1182,0.734,1183,0.758,1184,0.818,1185,0.786,1186,0.818,1187,0.786,1188,0.758,1189,1.276,1190,1.35,1191,0.905,1192,1.664,1193,0.969,1194,2.187,1195,1.664,1196,0.818,1197,0.969,1198,0.969,1199,2.31,1200,0.905,1201,0.969,1202,1.445,1203,2.911,1204,0.786,1205,1.504,1206,1.504,1207,0.856,1208,1.783,1209,0.786,1210,0.969,1211,0.969,1212,0.969,1213,1.783,1214,2.259,1215,2.006,1216,0.905,1217,2.867,1218,2.31,1219,0.856,1220,0.969,1221,0.758,1222,1.504,1223,0.905,1224,0.969,1225,0.818,1226,0.969,1227,0.969,1228,0.905,1229,1.664,1230,0.969,1231,0.905,1232,0.969,1233,0.969,1234,0.969,1235,0.969,1236,0.969,1237,0.969,1238,0.969,1239,0.969,1240,0.969,1241,0.969,1242,0.969,1243,0.969,1244,0.969,1245,0.969,1246,0.969,1247,1.783,1248,1.783,1249,2.475,1250,0.969,1251,0.969,1252,0.969,1253,0.969,1254,0.969,1255,0.969,1256,0.969,1257,1.783,1258,0.969,1259,0.969,1260,0.905,1261,0.969,1262,0.969,1263,0.969,1264,0.969,1265,0.905,1266,0.905,1267,0.856,1268,0.856,1269,0.856,1270,0.969,1271,0.969,1272,0.856,1273,0.969]],["component/7",[11,0.17,12,0.288]],["keyword/7",[]],["title/8-1",[111,30.441,153,35.814]],["name/8-1",[]],["text/8-1",[]],["component/8-1",[]],["keyword/8-1",[]],["title/8-2",[1,24.675,153,26.933,805,25.081,1003,30.012]],["name/8-2",[]],["text/8-2",[]],["component/8-2",[]],["keyword/8-2",[]],["title/8-3",[830,35.814,1007,40.941]],["name/8-3",[]],["text/8-3",[]],["component/8-3",[]],["keyword/8-3",[]],["title/8-4",[753,36.974]],["name/8-4",[]],["text/8-4",[]],["component/8-4",[]],["keyword/8-4",[]],["title/8",[]],["name/8",[814,0.31,1274,0.451]],["text/8",[1,3.24,6,2.983,7,1.378,8,2.372,9,1.925,10,2.878,11,2.294,20,2.523,21,1.355,24,1.355,36,1.378,38,2.132,44,2.83,46,2.878,69,1.43,95,1.199,111,4.133,116,1.458,120,1.332,121,1.332,123,1.489,129,1.378,139,2.209,147,2.929,148,1.458,151,1.68,153,6.018,156,1.43,159,1.458,160,1.378,164,1.852,168,1.556,170,2.016,174,2.286,175,2.741,177,2.016,180,1.635,191,3.471,211,1.521,237,4.567,238,1.928,259,1.521,264,1.556,268,2.712,274,1.556,283,1.489,288,2.419,294,2.787,295,1.378,306,2.173,315,2.016,333,1.787,340,1.404,342,1.43,351,1.378,362,1.852,363,4.483,369,1.928,372,2.419,385,1.489,388,1.731,417,1.556,418,1.988,430,1.635,461,2.419,463,1.787,474,1.594,479,3.681,480,1.644,504,1.43,510,1.635,513,1.852,525,1.594,534,1.183,561,1.594,563,1.787,585,1.731,622,2.787,644,1.731,666,1.731,691,1.852,706,2.419,732,1.68,754,3.099,755,1.635,770,3.197,775,3.072,779,2.787,802,1.594,805,4.365,824,1.852,830,2.372,832,3.348,834,2.132,835,2.643,837,2.87,851,1.787,879,3.197,900,1.521,901,5.326,921,1.787,931,4.763,949,2.787,963,1.556,974,1.556,983,1.68,990,1.731,999,1.594,1003,3.941,1005,2.019,1017,1.731,1080,1.68,1136,2.132,1140,2.964,1152,1.556,1160,1.521,1161,1.68,1172,2.964,1177,5.117,1178,1.731,1179,1.928,1181,1.928,1182,1.731,1190,1.731,1204,1.852,1215,1.852,1219,3.348,1275,5.65,1276,2.285,1277,2.964,1278,2.285,1279,2.285,1280,2.285,1281,2.285,1282,2.285,1283,2.285,1284,4.097,1285,2.285,1286,2.285,1287,2.285,1288,2.285,1289,3.79,1290,3.79,1291,3.79,1292,6.265,1293,1.852,1294,2.019,1295,2.132,1296,2.787,1297,3.678,1298,2.019,1299,1.556,1300,2.132,1301,2.132,1302,2.132,1303,3.79,1304,2.132,1305,2.285,1306,2.285,1307,2.132,1308,2.132,1309,1.928]],["component/8",[11,0.17,12,0.288]],["keyword/8",[]],["title/9-1",[963,38.963,971,38.09]],["name/9-1",[]],["text/9-1",[]],["component/9-1",[]],["keyword/9-1",[]],["title/9",[]],["name/9",[814,0.31,1310,0.451]],["text/9",[0,1.541,6,2.912,9,2.194,11,1.055,15,1.864,19,1.439,21,2.185,24,1.31,28,2.401,49,1.471,73,1.728,76,5.82,82,1.333,89,1.41,95,1.159,101,1.228,111,2.522,112,2.148,115,2.264,116,3.026,117,3.075,118,3.585,129,1.333,142,5.663,147,1.333,150,1.471,156,1.383,159,1.41,162,1.333,163,1.728,174,1.333,176,1.581,177,4.509,180,5.663,181,1.504,182,1.504,184,1.728,186,3.486,188,2.062,190,4.041,197,2.453,198,1.267,201,2.223,202,1.791,209,3.463,212,1.791,217,1.31,227,1.728,231,3.392,260,1.333,305,1.625,316,1.864,318,1.952,321,2.352,351,1.333,364,1.471,393,1.31,432,1.439,435,1.791,437,1.625,441,2.453,476,2.401,478,2.509,480,1.598,504,1.383,505,1.673,516,1.541,521,1.625,526,1.581,530,1.541,531,1.673,537,2.352,567,2.987,580,2.401,596,1.541,616,2.987,619,2.223,641,2.637,648,1.541,719,3.59,728,3.306,729,1.864,733,1.504,747,1.504,752,1.247,753,4.139,849,1.791,901,6.406,963,4.524,971,1.471,979,3.532,1012,5.389,1099,1.581,1140,4.361,1148,2.987,1176,1.864,1177,1.673,1293,1.791,1299,1.504,1311,3.439,1312,1.952,1313,2.209,1314,1.952,1315,2.062,1316,1.864,1317,2.209,1318,2.209,1319,2.062,1320,2.062,1321,1.864,1322,1.791,1323,2.209,1324,2.71,1325,2.062,1326,2.209,1327,2.209,1328,3.685,1329,2.209]],["component/9",[11,0.17,12,0.288]],["keyword/9",[]],["title/10-1",[401,63.946]],["name/10-1",[]],["text/10-1",[]],["component/10-1",[]],["keyword/10-1",[]],["title/10",[]],["name/10",[814,0.31,1330,0.451]],["text/10",[1,3.464,6,2.549,9,2.395,20,2.763,22,2.419,24,2.46,46,2.46,61,3.143,83,4.898,86,3.246,87,6.01,101,4.978,115,2.549,117,3.358,126,4.851,129,3.644,130,2.97,137,4.491,153,3.781,160,4.297,170,2.208,175,2.343,177,3.214,202,3.364,237,2.649,278,2.598,281,3.143,292,2.46,296,2.46,321,3.856,342,3.781,345,5.101,380,3.502,393,3.581,432,2.704,434,6.293,461,2.649,476,2.704,495,2.97,496,3.502,514,3.364,551,4.114,561,2.895,591,3.143,601,4.297,676,3.246,682,3.364,722,3.052,736,3.246,753,2.24,768,2.97,803,3.502,804,3.502,808,3.502,809,3.502,810,3.502,811,2.97,812,3.502,827,3.502,828,3.364,829,3.364,921,3.246,932,2.763,962,3.246,965,3.873,1003,2.895,1034,2.826,1063,3.873,1147,3.667,1148,3.364,1331,4.15,1332,4.15,1333,4.15,1334,4.15,1335,4.15,1336,4.15,1337,4.15,1338,3.873,1339,3.873,1340,3.502,1341,3.667]],["component/10",[11,0.17,12,0.288]],["keyword/10",[]],["title/11-1",[392,40.617]],["name/11-1",[]],["text/11-1",[]],["component/11-1",[]],["keyword/11-1",[]],["title/11-2",[43,35.147,290,36.524]],["name/11-2",[]],["text/11-2",[]],["component/11-2",[]],["keyword/11-2",[]],["title/11-3",[137,25.423,345,27.302,802,34.26]],["name/11-3",[]],["text/11-3",[]],["component/11-3",[]],["keyword/11-3",[]],["title/11-4",[101,23.916,137,22.271,803,36.303,804,36.303]],["name/11-4",[]],["text/11-4",[]],["component/11-4",[]],["keyword/11-4",[]],["title/11-5",[10,25.508,24,25.508,805,25.081,806,28.035]],["name/11-5",[]],["text/11-5",[]],["component/11-5",[]],["keyword/11-5",[]],["title/11-6",[74,35.146,805,28.631,807,43.396]],["name/11-6",[]],["text/11-6",[]],["component/11-6",[]],["keyword/11-6",[]],["title/11-7",[806,44.641]],["name/11-7",[]],["text/11-7",[]],["component/11-7",[]],["keyword/11-7",[]],["title/11-8",[22,28.631,137,25.423,345,27.302]],["name/11-8",[]],["text/11-8",[]],["component/11-8",[]],["keyword/11-8",[]],["title/11-9",[61,28.995,101,21.278,137,19.814,296,22.694,412,28.151]],["name/11-9",[]],["text/11-9",[]],["component/11-9",[]],["keyword/11-9",[]],["title/11-10",[808,48.275,809,48.275]],["name/11-10",[]],["text/11-10",[]],["component/11-10",[]],["keyword/11-10",[]],["title/11-11",[74,40.941,810,48.275]],["name/11-11",[]],["text/11-11",[]],["component/11-11",[]],["keyword/11-11",[]],["title/11-12",[46,29.118,811,35.146,812,41.442]],["name/11-12",[]],["text/11-12",[]],["component/11-12",[]],["keyword/11-12",[]],["title/11-13",[11,12.322,199,26.431,404,25.508,601,25.957]],["name/11-13",[]],["text/11-13",[]],["component/11-13",[]],["keyword/11-13",[]],["title/11-14",[36,34.517,630,40.941]],["name/11-14",[]],["text/11-14",[]],["component/11-14",[]],["keyword/11-14",[]],["title/11-15",[632,50.384]],["name/11-15",[]],["text/11-15",[]],["component/11-15",[]],["keyword/11-15",[]],["title/11-16",[454,43.338,628,43.338]],["name/11-16",[]],["text/11-16",[]],["component/11-16",[]],["keyword/11-16",[]],["title/11-17",[633,51.895]],["name/11-17",[]],["text/11-17",[]],["component/11-17",[]],["keyword/11-17",[]],["title/11-18",[634,55.538]],["name/11-18",[]],["text/11-18",[]],["component/11-18",[]],["keyword/11-18",[]],["title/11-19",[74,40.941,629,46.38]],["name/11-19",[]],["text/11-19",[]],["component/11-19",[]],["keyword/11-19",[]],["title/11-20",[11,16.386,813,50.551]],["name/11-20",[]],["text/11-20",[]],["component/11-20",[]],["keyword/11-20",[]],["title/11-21",[9,22.686,932,38.09]],["name/11-21",[]],["text/11-21",[]],["component/11-21",[]],["keyword/11-21",[]],["title/11-22",[9,15.178,363,27.391,805,22.314,836,29.944,837,28.995]],["name/11-22",[]],["text/11-22",[]],["component/11-22",[]],["keyword/11-22",[]],["title/11-23",[9,17.06,11,12.322,223,31.641,534,22.271]],["name/11-23",[]],["text/11-23",[]],["component/11-23",[]],["keyword/11-23",[]],["title/11-24",[9,15.178,11,10.963,95,20.085,424,28.151,534,19.814]],["name/11-24",[]],["text/11-24",[]],["component/11-24",[]],["keyword/11-24",[]],["title/11-25",[418,25.771,1012,33.448,1140,25.771]],["name/11-25",[]],["text/11-25",[]],["component/11-25",[]],["keyword/11-25",[]],["title/11-26",[418,25.771,753,26.506,1140,25.771]],["name/11-26",[]],["text/11-26",[]],["component/11-26",[]],["keyword/11-26",[]],["title/11-27",[418,25.771,1140,25.771,1141,49.119]],["name/11-27",[]],["text/11-27",[]],["component/11-27",[]],["keyword/11-27",[]],["title/11-28",[10,33.92,83,32.297]],["name/11-28",[]],["text/11-28",[]],["component/11-28",[]],["keyword/11-28",[]],["title/11-29",[111,30.441,342,35.814]],["name/11-29",[]],["text/11-29",[]],["component/11-29",[]],["keyword/11-29",[]],["title/11-30",[292,33.92,1142,40.941]],["name/11-30",[]],["text/11-30",[]],["component/11-30",[]],["keyword/11-30",[]],["title/11-31",[342,35.814,954,38.963]],["name/11-31",[]],["text/11-31",[]],["component/11-31",[]],["keyword/11-31",[]],["title/11-32",[1,32.812,83,32.297]],["name/11-32",[]],["text/11-32",[]],["component/11-32",[]],["keyword/11-32",[]],["title/11-33",[68,35.147,1142,40.941]],["name/11-33",[]],["text/11-33",[]],["component/11-33",[]],["keyword/11-33",[]],["title/11-34",[1,24.675,153,26.933,805,25.081,1003,30.012]],["name/11-34",[]],["text/11-34",[]],["component/11-34",[]],["keyword/11-34",[]],["title/11-35",[830,35.814,1007,40.941]],["name/11-35",[]],["text/11-35",[]],["component/11-35",[]],["keyword/11-35",[]],["title/11-36",[753,36.974]],["name/11-36",[]],["text/11-36",[]],["component/11-36",[]],["keyword/11-36",[]],["title/11",[11,14.066,285,37.203,288,31.354]],["name/11",[814,0.592]],["text/11",[0,0.949,1,2.157,2,0.641,3,0.191,6,1.391,7,1.052,8,2.053,9,2.276,10,2.955,11,2.16,12,0.847,13,0.172,15,0.798,18,0.374,19,0.322,20,2.183,21,0.923,22,0.907,24,1.618,27,0.186,28,0.887,29,2.543,36,1.556,37,0.641,38,0.236,44,3.797,45,1.795,46,1.787,48,0.176,49,1.396,50,0.588,51,1.622,53,2.253,54,0.641,55,0.168,59,0.437,61,0.549,62,0.181,63,0.717,66,0.519,67,0.165,68,0.304,69,0.725,73,0.387,74,1.248,76,1.542,82,1.647,83,3.796,86,0.906,87,0.798,89,0.993,95,2.032,96,0.205,100,0.437,101,3.157,102,0.191,106,1.817,109,0.821,110,0.927,111,2.331,112,1.675,113,0.191,114,0.205,115,0.956,116,0.993,117,2.152,118,1.222,119,0.551,120,0.793,121,0.793,122,0.793,123,0.754,124,0.494,125,0.181,126,1.651,127,1.085,128,0.549,129,2.464,130,0.354,132,0.205,137,2.806,138,0.437,139,0.907,142,1.501,143,0.198,145,1.065,146,0.223,147,2.128,148,0.869,149,0.363,150,1.036,151,0.533,153,3.594,154,0.336,155,0.717,156,0.592,159,0.739,160,2.2,162,0.939,163,0.198,164,0.401,165,0.374,167,0.14,168,0.494,170,1.023,172,0.271,173,0.949,174,2.054,175,2.363,176,0.519,177,2.733,180,1.735,181,0.789,182,0.927,183,0.906,184,0.198,186,0.696,188,0.236,190,1.298,191,1.585,192,0.181,194,1.204,195,0.852,196,0.205,197,0.771,198,1.103,199,0.581,200,0.213,201,0.298,202,0.401,209,0.725,211,0.771,212,0.205,213,0.454,217,0.561,218,0.158,219,0.789,220,0.213,221,0.191,223,0.363,224,0.205,226,0.223,227,0.198,228,0.181,231,0.829,232,0.165,236,1.559,237,1.647,238,0.213,239,0.181,242,0.483,243,0.494,245,0.494,246,0.771,255,0.354,256,0.176,259,0.63,260,0.437,264,0.336,268,0.354,271,0.677,274,0.494,275,0.808,276,0.592,278,0.852,279,0.223,281,0.549,282,0.437,283,1.136,285,1.321,286,0.223,288,2.401,292,1.944,293,0.223,294,0.533,295,0.939,296,2.019,300,0.483,303,1.581,304,1.964,305,0.186,306,1,307,0.322,310,1.036,315,0.616,316,0.213,318,0.223,319,0.939,321,0.869,322,0.534,333,0.198,335,1.203,340,1.182,341,0.63,342,2.491,343,0.223,344,0.205,345,2.327,346,0.181,348,1.184,349,0.354,351,0.699,355,0.205,357,0.437,358,1.187,360,1.364,362,0.401,363,3.135,364,0.329,366,0.696,369,0.612,370,0.437,372,1.445,379,0.213,380,0.213,385,0.472,388,1.588,393,2.091,394,0.165,399,0.186,401,0.461,404,1.438,407,0.437,409,0.181,411,0.322,412,0.533,413,0.363,414,0.612,415,0.191,416,0.223,417,0.789,418,2.032,424,0.363,426,1.103,427,0.205,430,0.519,432,1.253,433,0.191,434,0.641,435,0.588,436,1.542,437,0.363,441,0.906,449,0.236,454,1.179,455,0.836,460,0.437,461,0.869,463,0.198,468,0.205,469,0.417,470,0.506,473,0.387,474,0.808,475,0.363,476,0.754,477,0.533,478,1.06,479,0.754,480,1.901,481,0.336,483,0.236,490,0.494,492,0.205,495,0.974,496,0.213,499,0.186,500,0.322,504,1.091,505,0.191,510,0.181,512,1.58,513,0.588,514,0.205,515,1.085,516,0.345,520,0.519,521,0.696,523,0.519,524,0.401,525,0.808,526,0.354,530,0.176,531,0.877,534,3.501,537,1.113,541,0.567,544,0.223,551,0.927,553,0.927,554,0.213,557,0.363,561,0.66,563,0.198,564,0.205,567,0.401,569,0.181,570,0.181,571,0.176,572,0.176,573,0.176,574,0.176,580,0.754,582,0.336,585,0.191,587,0.519,589,0.363,591,0.549,594,0.401,596,0.506,598,0.533,599,0.374,600,0.236,601,1.818,602,0.641,604,0.205,608,0.186,609,0.66,612,0.198,616,0.401,618,0.198,619,0.699,622,0.533,625,0.336,628,0.877,629,1.413,630,1.735,632,0.852,633,1.179,634,1.261,637,0.191,641,0.519,644,1.031,646,0.172,648,0.176,650,0.191,651,0.387,659,0.836,662,0.696,665,1.161,666,0.191,667,0.416,669,0.533,670,0.387,673,0.191,676,0.74,678,0.533,679,0.213,682,0.401,683,0.223,685,0.198,688,0.223,691,0.205,693,0.205,694,0.363,695,0.417,697,0.186,703,0.533,705,0.223,706,0.993,719,0.549,721,0.63,722,0.186,727,0.641,728,0.66,729,0.417,732,0.533,733,0.494,736,0.567,742,0.883,743,0.223,744,0.223,747,0.644,752,1.086,753,1.968,754,1.742,755,0.181,756,0.213,763,0.506,764,0.533,768,0.519,770,0.977,774,0.213,775,0.401,776,0.186,779,0.533,788,0.205,791,0.387,792,0.186,794,0.213,799,0.567,802,0.808,803,0.417,804,1.769,805,3.08,806,1.681,807,3.117,808,0.798,809,0.798,810,0.612,811,0.354,812,0.417,813,0.836,816,0.253,817,0.253,818,0.494,819,0.205,820,0.798,821,0.437,822,0.612,823,1.375,824,0.588,825,0.588,826,0.253,827,0.417,828,0.401,829,0.588,830,1.091,831,0.74,832,1.203,833,1.361,834,0.677,835,0.949,836,1.364,837,1.179,838,0.725,839,0.223,840,0.253,841,0.946,842,0.494,843,0.725,844,0.253,845,0.253,846,0.253,847,0.236,848,0.612,849,0.401,850,0.494,851,0.387,852,0.253,853,0.387,854,0.567,855,0.236,856,0.253,857,0.253,858,0.253,859,0.253,860,0.198,861,0.461,862,0.205,863,0.236,864,0.461,865,1.148,866,0.798,867,2,868,0.253,869,0.205,870,0.253,871,0.253,872,0.253,873,0.253,874,0.725,875,0.253,876,0.253,877,0.253,878,1.375,879,1.91,880,0.253,881,0.253,882,0.253,883,0.253,884,0.253,885,0.253,886,0.336,887,0.253,888,0.236,889,0.253,890,0.461,891,0.494,892,0.494,893,0.494,894,0.494,895,0.641,896,0.253,897,0.494,898,0.253,899,0.253,900,1.507,901,5.497,902,0.236,903,0.494,904,0.677,905,0.205,906,0.253,907,0.253,908,0.253,909,0.461,910,0.253,911,0.236,912,0.253,913,0.253,914,0.494,915,0.253,916,0.253,917,0.253,918,0.253,919,0.253,920,0.253,921,1.217,922,0.253,923,0.223,924,0.253,925,0.198,926,0.437,927,0.253,928,0.223,929,0.253,930,1.023,931,2.504,932,2.096,933,0.223,934,0.253,935,0.236,936,0.253,937,0.253,938,0.253,939,0.223,940,0.223,941,0.223,942,0.253,943,0.236,944,0.939,945,0.253,946,0.253,947,0.387,948,0.236,949,0.533,950,0.253,951,0.494,952,1.54,953,0.437,954,0.927,955,0.363,956,0.223,957,0.567,958,0.213,959,0.533,960,0.253,961,0.253,962,0.74,963,2.71,964,0.253,965,0.461,966,0.641,967,0.191,968,0.253,969,0.253,970,0.236,971,1.036,972,0.677,973,1.27,974,0.644,975,0.461,976,0.461,977,0.253,978,1.081,979,1.113,980,1.023,981,0.612,982,0.213,983,0.852,984,0.437,985,0.437,986,0.253,987,0.253,988,0.253,989,0.253,990,0.549,991,1.744,992,0.494,993,0.494,994,0.494,995,0.494,996,0.946,997,0.494,998,0.494,999,0.808,1000,0.253,1001,2.113,1002,0.494,1003,1.216,1004,0.494,1005,0.437,1006,0.253,1007,0.354,1008,0.677,1009,0.253,1010,0.253,1011,0.494,1012,1.757,1013,0.461,1014,0.461,1015,0.461,1016,0.253,1017,0.717,1018,0.253,1019,0.253,1020,0.374,1021,0.253,1022,0.253,1023,0.191,1024,0.549,1025,0.253,1026,0.437,1027,0.253,1028,0.253,1029,0.253,1030,0.253,1031,0.253,1032,0.253,1033,0.253,1034,0.927,1035,0.253,1036,0.461,1037,0.461,1038,0.253,1039,0.461,1040,0.461,1041,0.461,1042,1.396,1043,0.461,1044,0.461,1045,0.461,1046,0.461,1047,0.461,1048,0.461,1049,0.677,1050,0.253,1051,0.253,1052,0.253,1053,0.977,1054,0.461,1055,0.461,1056,1.579,1057,0.461,1058,0.253,1059,0.461,1060,0.461,1061,0.461,1062,0.677,1063,1.452,1064,1.361,1065,0.494,1066,0.494,1067,0.494,1068,0.494,1069,0.253,1070,0.253,1071,0.401,1072,0.437,1073,0.798,1074,0.223,1075,0.236,1076,0.253,1077,0.363,1078,0.236,1079,0.253,1080,0.363,1081,0.223,1082,0.223,1083,0.253,1084,0.213,1085,0.223,1086,0.198,1087,0.236,1088,0.253,1089,0.236,1090,0.236,1091,0.236,1092,0.437,1093,0.253,1094,0.223,1095,0.417,1096,0.191,1097,0.236,1098,0.253,1099,0.354,1100,0.236,1101,0.236,1102,0.253,1103,0.205,1104,0.253,1105,0.236,1106,1.114,1107,0.223,1108,0.223,1109,0.236,1110,0.236,1111,0.236,1112,0.253,1113,0.205,1114,0.205,1115,0.236,1116,0.236,1117,0.253,1118,0.213,1119,0.236,1120,0.236,1121,0.176,1122,0.236,1123,0.236,1124,0.374,1125,0.236,1126,0.253,1127,0.223,1128,0.223,1129,0.494,1130,0.494,1131,0.494,1132,0.253,1133,0.253,1134,0.253,1135,0.253,1136,0.461,1137,0.213,1138,0.387,1139,0.205,1140,2.442,1142,0.181,1144,0.253,1145,0.253,1146,0.253,1147,0.641,1148,0.939,1149,0.236,1150,0.236,1151,0.236,1152,0.927,1153,0.236,1154,0.253,1155,0.236,1156,0.236,1157,0.236,1158,0.437,1159,0.437,1160,0.906,1161,1.001,1162,0.519,1163,0.401,1164,0.223,1165,0.236,1166,0.213,1167,0.549,1168,0.253,1169,0.494,1170,0.205,1171,0.253,1172,0.567,1173,0.223,1174,0.236,1175,0.186,1176,0.417,1177,1.954,1178,0.717,1179,0.417,1180,0.253,1181,0.417,1182,0.374,1183,0.198,1184,0.213,1185,0.205,1186,0.213,1187,0.205,1188,0.198,1189,0.354,1190,0.549,1191,0.236,1192,0.461,1193,0.253,1194,0.641,1195,0.461,1196,0.213,1197,0.253,1198,0.253,1199,0.677,1200,0.236,1201,0.253,1202,0.401,1203,0.939,1204,0.401,1205,0.417,1206,0.417,1207,0.223,1208,0.494,1209,0.205,1210,0.253,1211,0.253,1212,0.253,1213,0.494,1214,0.696,1215,0.767,1216,0.236,1217,0.883,1218,0.677,1219,0.641,1220,0.253,1221,0.198,1222,0.417,1223,0.236,1224,0.253,1225,0.213,1226,0.253,1227,0.253,1228,0.236,1229,0.461,1230,0.253,1231,0.236,1232,0.253,1233,0.253,1234,0.253,1235,0.253,1236,0.253,1237,0.253,1238,0.253,1239,0.253,1240,0.253,1241,0.253,1242,0.253,1243,0.253,1244,0.253,1245,0.253,1246,0.253,1247,0.494,1248,0.494,1249,0.725,1250,0.253,1251,0.253,1252,0.253,1253,0.253,1254,0.253,1255,0.253,1256,0.253,1257,0.494,1258,0.253,1259,0.253,1260,0.236,1261,0.253,1262,0.253,1263,0.253,1264,0.253,1265,0.236,1266,0.236,1267,0.223,1268,0.223,1269,0.223,1270,0.253,1271,0.253,1272,0.223,1273,0.253,1275,0.946,1276,0.253,1277,0.387,1278,0.253,1279,0.253,1280,0.253,1281,0.253,1282,0.253,1283,0.253,1284,0.612,1285,0.253,1286,0.253,1287,0.253,1288,0.253,1289,0.494,1290,0.494,1291,0.494,1292,1.158,1293,0.401,1294,0.223,1295,0.236,1296,0.363,1297,0.549,1298,0.223,1299,0.336,1300,0.236,1301,0.236,1302,0.236,1303,0.494,1304,0.236,1305,0.253,1306,0.253,1307,0.236,1308,0.236,1309,0.213,1311,0.461,1312,0.223,1313,0.253,1314,0.223,1315,0.236,1316,0.213,1317,0.253,1318,0.253,1319,0.236,1320,0.236,1321,0.213,1322,0.205,1323,0.253,1324,0.363,1325,0.236,1326,0.253,1327,0.253,1328,0.494,1329,0.253,1331,0.253,1332,0.253,1333,0.253,1334,0.253,1335,0.253,1336,0.253,1337,0.253,1338,0.236,1339,0.236,1340,0.213,1341,0.223]],["component/11",[11,0.17,12,0.288]],["keyword/11",[]],["title/12-1",[392,40.617]],["name/12-1",[]],["text/12-1",[]],["component/12-1",[]],["keyword/12-1",[]],["title/12-2",[43,35.147,290,36.524]],["name/12-2",[]],["text/12-2",[]],["component/12-2",[]],["keyword/12-2",[]],["title/12-3",[295,20.799,303,18.089,304,22.464,537,22.008,598,25.354,599,26.114]],["name/12-3",[]],["text/12-3",[]],["component/12-3",[]],["keyword/12-3",[]],["title/12-4",[95,16.455,172,17.173,303,16.455,304,20.433,537,20.019,598,23.062,599,23.754]],["name/12-4",[]],["text/12-4",[]],["component/12-4",[]],["keyword/12-4",[]],["title/12-5",[303,30.021,752,32.297]],["name/12-5",[]],["text/12-5",[]],["component/12-5",[]],["keyword/12-5",[]],["title/12-6",[109,34.517,172,31.331]],["name/12-6",[]],["text/12-6",[]],["component/12-6",[]],["keyword/12-6",[]],["title/12-7",[303,16.455,411,20.433,534,16.232,535,22.44,536,37.041,1342,29.27]],["name/12-7",[]],["text/12-7",[]],["component/12-7",[]],["keyword/12-7",[]],["title/12-8",[109,17.351,172,15.749,411,18.74,534,14.887,535,20.58,536,34.602,1342,26.844]],["name/12-8",[]],["text/12-8",[]],["component/12-8",[]],["keyword/12-8",[]],["title/12-9",[303,30.021,1343,53.402]],["name/12-9",[]],["text/12-9",[]],["component/12-9",[]],["keyword/12-9",[]],["title/12-10",[109,25.957,172,23.561,303,22.576,1343,40.158]],["name/12-10",[]],["text/12-10",[]],["component/12-10",[]],["keyword/12-10",[]],["title/12-11",[21,29.118,303,25.771,319,29.631]],["name/12-11",[]],["text/12-11",[]],["component/12-11",[]],["keyword/12-11",[]],["title/12-12",[21,22.694,109,23.093,172,20.962,303,20.085,319,23.093]],["name/12-12",[]],["text/12-12",[]],["component/12-12",[]],["keyword/12-12",[]],["title/12-13",[303,25.771,319,29.631,533,41.442]],["name/12-13",[]],["text/12-13",[]],["component/12-13",[]],["keyword/12-13",[]],["title/12-14",[109,23.093,172,20.962,303,20.085,319,23.093,533,32.298]],["name/12-14",[]],["text/12-14",[]],["component/12-14",[]],["keyword/12-14",[]],["title/12-15",[117,23.916,177,22.891,296,25.508,303,22.576]],["name/12-15",[]],["text/12-15",[]],["component/12-15",[]],["keyword/12-15",[]],["title/12-16",[109,20.799,117,19.164,172,18.879,177,18.343,296,20.439,303,18.089]],["name/12-16",[]],["text/12-16",[]],["component/12-16",[]],["keyword/12-16",[]],["title/12-17",[139,28.631,394,32.003,534,25.423]],["name/12-17",[]],["text/12-17",[]],["component/12-17",[]],["keyword/12-17",[]],["title/12-18",[1012,38.963,1140,30.021]],["name/12-18",[]],["text/12-18",[]],["component/12-18",[]],["keyword/12-18",[]],["title/12-19",[180,40.941,1140,30.021]],["name/12-19",[]],["text/12-19",[]],["component/12-19",[]],["keyword/12-19",[]],["title/12-20",[139,22.314,303,20.085,304,24.942,755,27.391,1344,32.298]],["name/12-20",[]],["text/12-20",[]],["component/12-20",[]],["keyword/12-20",[]],["title/12-21",[28,28.035,201,25.957,753,23.22,754,27.466]],["name/12-21",[]],["text/12-21",[]],["component/12-21",[]],["keyword/12-21",[]],["title/12-22",[51,27.725,117,27.302,351,29.631]],["name/12-22",[]],["text/12-22",[]],["component/12-22",[]],["keyword/12-22",[]],["title/12-23",[51,19.461,117,19.164,139,20.097,170,18.343,175,19.461,190,19.772]],["name/12-23",[]],["text/12-23",[]],["component/12-23",[]],["keyword/12-23",[]],["title/12-24",[175,32.297,418,30.021]],["name/12-24",[]],["text/12-24",[]],["component/12-24",[]],["keyword/12-24",[]],["title/12-25",[266,39.815,619,29.631,835,34.26]],["name/12-25",[]],["text/12-25",[]],["component/12-25",[]],["keyword/12-25",[]],["title/12-26",[154,38.963,175,32.297]],["name/12-26",[]],["text/12-26",[]],["component/12-26",[]],["keyword/12-26",[]],["title/12-27",[619,29.631,835,34.26,1345,43.396]],["name/12-27",[]],["text/12-27",[]],["component/12-27",[]],["keyword/12-27",[]],["title/12-28",[175,32.297,1020,43.338]],["name/12-28",[]],["text/12-28",[]],["component/12-28",[]],["keyword/12-28",[]],["title/12-29",[1296,50.384]],["name/12-29",[]],["text/12-29",[]],["component/12-29",[]],["keyword/12-29",[]],["title/12",[303,20.085,304,24.942,537,24.436,598,28.151,599,28.995]],["name/12",[1346,0.861]],["text/12",[0,0.256,6,0.226,8,0.837,9,2.516,10,1.121,11,0.614,12,1.269,17,0.298,18,0.278,19,0.239,20,0.688,21,2.026,22,0.945,24,0.218,25,0.539,27,0.983,28,2.343,29,0.256,37,0.913,43,0.437,44,0.415,46,2.232,48,0.932,49,2.817,50,0.298,51,3.275,55,0.688,62,1.535,67,0.463,68,0.635,69,0.23,82,0.222,83,3.174,89,0.66,95,2.501,96,0.298,101,2.352,106,1.588,109,0.806,110,1.461,111,2.089,112,0.779,115,1.739,117,3.362,118,0.945,120,0.214,121,0.214,123,0.673,124,0.704,125,0.509,126,1.625,127,1.496,128,0.539,129,2.463,130,1.161,132,0.838,135,0.367,137,2.591,139,2.779,142,1.535,147,0.624,148,0.853,149,0.523,150,1.08,153,0.647,154,2.448,155,0.278,156,0.647,157,0.343,158,1.083,159,1.035,160,1.949,161,0.76,162,0.429,163,0.287,165,0.278,167,0.902,170,0.863,172,0.566,173,0.496,174,0.222,175,4.491,176,0.263,177,2.663,179,0.325,180,0.74,181,0.484,182,1.288,183,1.046,185,1.533,186,0.523,190,3.598,191,0.821,192,0.509,194,1.184,195,0.647,196,0.298,197,0.473,198,1.084,201,0.978,203,0.325,207,0.6,208,0.343,209,0.23,211,0.245,213,2.738,217,0.422,219,0.484,223,0.27,231,0.956,232,0.239,234,1.012,237,0.234,239,0.74,242,0.473,243,0.25,259,0.688,260,0.978,263,1.181,264,0.25,265,0.91,266,2.461,270,0.287,272,0.6,273,1.128,274,0.484,275,0.932,276,1.015,278,1.015,281,0.278,283,1.844,288,0.234,292,1.678,294,0.76,295,1.141,296,2.967,297,1.181,298,0.6,299,1.034,300,1.08,303,3.937,304,3.664,305,1.193,306,0.211,308,0.31,309,0.556,310,1.259,313,0.367,315,0.195,316,0.31,317,0.721,318,0.913,319,4.478,320,1.671,321,2.182,335,0.325,337,0.539,338,0.367,340,0.226,341,1.428,342,0.837,343,0.325,344,1.083,345,0.574,346,0.74,348,2.03,349,1.353,351,2.463,352,0.711,358,0.484,363,1.161,364,1.259,366,0.27,372,1.669,383,0.263,384,1.315,385,1.555,388,0.278,393,1.272,394,1.057,404,0.218,407,0.325,408,0.6,409,0.956,411,1.703,412,1.391,415,0.539,417,1.625,418,2.366,430,0.956,432,0.463,433,0.278,436,1.288,437,0.523,439,0.367,441,0.89,463,0.287,464,0.31,468,0.298,470,0.256,474,0.496,475,0.27,476,0.871,477,0.523,478,1.288,480,1.228,481,0.484,482,0.343,486,0.367,490,1.78,491,0.325,492,0.298,495,0.74,499,1.391,500,0.871,502,0.367,504,1.493,505,0.278,506,0.31,510,0.263,512,1.555,518,0.31,523,0.956,524,0.576,525,0.721,526,0.74,530,1.131,531,1.012,532,1.128,533,3.177,534,1.353,535,2.446,536,2.852,537,1.938,540,0.367,541,0.287,546,0.343,547,2.145,549,0.287,551,0.25,552,0.6,553,0.25,554,0.31,557,0.76,567,0.576,568,0.6,580,0.871,581,0.27,582,2.327,583,0.31,590,0.325,593,0.783,594,0.576,595,0.76,598,1.755,599,1.807,601,0.429,606,1.622,609,0.721,612,0.287,614,0.298,616,0.576,617,0.711,618,1.046,619,2.8,624,0.872,625,0.704,626,0.783,636,0.343,640,0.325,641,0.509,642,0.711,646,1.461,653,0.31,654,0.556,655,0.343,661,0.298,662,0.27,665,0.245,667,0.408,668,0.367,669,0.523,675,0.343,676,0.556,678,0.27,683,0.325,685,0.808,689,0.343,693,0.838,694,0.523,697,0.27,703,0.27,706,0.853,709,0.367,713,0.343,714,1.128,715,0.343,716,0.343,717,0.872,719,1.432,720,0.343,721,1.08,726,0.913,728,1.823,729,2.205,730,0.838,731,0.783,736,0.556,738,2.5,739,1.895,740,0.343,741,1.161,743,0.628,744,0.628,745,0.664,746,0.664,747,0.25,749,0.965,750,0.965,751,0.965,752,3.225,753,1.844,754,4.005,755,2.025,762,0.343,763,0.721,764,0.27,768,1.161,770,0.31,774,0.31,776,0.27,784,0.367,792,0.27,799,0.556,802,0.256,819,0.576,821,0.628,824,0.576,825,0.838,831,0.808,835,0.721,836,0.287,847,0.343,851,0.287,853,0.808,860,0.556,865,0.31,866,0.31,869,1.315,901,4.683,931,2.718,932,3.09,940,0.325,947,0.287,949,0.27,952,0.913,958,0.31,959,0.27,962,0.287,963,0.704,967,0.539,971,0.688,974,0.484,980,1.181,982,0.31,990,0.539,999,1.131,1003,0.721,1007,0.74,1012,2.067,1017,0.278,1020,2.852,1023,0.278,1024,1.228,1026,1.671,1034,1.461,1042,0.245,1049,0.343,1071,0.298,1072,0.325,1073,0.6,1075,0.343,1077,0.27,1081,0.628,1086,0.808,1092,0.325,1096,0.278,1099,0.263,1121,1.131,1124,0.539,1139,0.298,1140,1.887,1142,0.263,1147,0.913,1148,0.576,1152,2.448,1153,0.343,1155,0.343,1158,0.628,1159,0.628,1160,2.394,1161,1.755,1162,0.74,1163,0.576,1164,0.325,1166,0.31,1167,1.432,1170,0.838,1172,0.808,1173,0.628,1175,0.523,1176,1.368,1177,1.432,1178,0.278,1179,0.31,1181,0.6,1182,0.539,1183,0.287,1187,0.576,1188,0.287,1189,0.263,1190,0.783,1191,0.343,1192,0.664,1194,1.671,1195,0.965,1196,0.31,1199,0.664,1200,0.343,1202,0.298,1203,0.298,1205,0.872,1206,0.872,1207,0.325,1214,1.391,1216,0.343,1265,0.965,1266,1.247,1269,0.628,1277,0.287,1284,0.6,1299,0.91,1316,2.205,1324,0.523,1339,0.343,1341,0.325,1342,1.514,1343,2.44,1344,2.388,1345,2.309,1347,0.405,1348,0.325,1349,0.343,1350,0.405,1351,0.367,1352,0.405,1353,0.783,1354,0.405,1355,0.783,1356,0.913,1357,0.711,1358,1.138,1359,1.138,1360,0.367,1361,0.343,1362,0.711,1363,0.405,1364,0.367,1365,0.367,1366,0.405,1367,0.367,1368,1.81,1369,0.405,1370,0.367,1371,1.083,1372,0.405,1373,0.343,1374,0.31,1375,0.405,1376,0.405,1377,0.711,1378,0.405,1379,0.405,1380,0.965,1381,0.367,1382,0.783,1383,0.405,1384,0.367,1385,0.405,1386,0.405,1387,0.783,1388,0.711,1389,0.325,1390,0.405,1391,0.405,1392,0.405,1393,0.367,1394,0.405,1395,0.405,1396,0.405,1397,0.838,1398,0.405,1399,0.367,1400,0.405,1401,0.576,1402,0.405,1403,0.6,1404,0.405,1405,0.783,1406,0.405,1407,0.405,1408,0.343,1409,1.034,1410,0.405,1411,0.405,1412,0.405,1413,0.405,1414,0.783,1415,0.783,1416,0.405,1417,0.405,1418,0.405,1419,0.783,1420,1.472,1421,0.325,1422,0.405,1423,0.343,1424,0.405,1425,0.711,1426,0.405,1427,0.405,1428,0.405,1429,0.367,1430,0.405,1431,0.343,1432,0.367,1433,0.367,1434,0.664,1435,0.405,1436,1.034,1437,0.783,1438,0.405,1439,0.405,1440,0.405,1441,0.405,1442,0.783,1443,0.405,1444,0.783,1445,0.405,1446,0.405,1447,0.405,1448,0.783,1449,0.405,1450,0.405,1451,0.405,1452,0.405,1453,0.405,1454,0.405,1455,0.367,1456,0.405,1457,0.405,1458,0.405,1459,0.405,1460,0.367,1461,0.783,1462,0.783,1463,0.783,1464,0.405,1465,0.367,1466,0.325,1467,0.343,1468,0.367,1469,0.405,1470,0.6,1471,0.405,1472,0.405,1473,0.31,1474,0.367,1475,0.325,1476,0.367,1477,0.664,1478,0.405,1479,0.367,1480,0.405,1481,0.405,1482,0.405,1483,0.405,1484,0.664,1485,0.664,1486,0.405,1487,1.622,1488,0.405,1489,0.913,1490,0.325,1491,0.783,1492,0.405,1493,0.965,1494,0.405,1495,0.405,1496,0.405,1497,0.405,1498,0.405,1499,0.405,1500,0.325,1501,0.405,1502,0.405,1503,0.405,1504,0.405,1505,0.405,1506,0.405,1507,0.343,1508,0.405,1509,0.405,1510,0.367,1511,0.343,1512,0.367,1513,0.343,1514,0.367,1515,0.405,1516,0.367,1517,0.405,1518,0.405,1519,0.405,1520,0.405,1521,0.405,1522,0.405,1523,0.405,1524,0.405,1525,0.367,1526,0.405,1527,0.405,1528,0.405,1529,0.405,1530,0.405,1531,0.405,1532,1.78,1533,1.034,1534,1.181,1535,0.405,1536,0.405,1537,2.879,1538,0.6,1539,0.783,1540,0.664,1541,0.405,1542,1.786,1543,0.405,1544,1.034,1545,0.343,1546,0.405,1547,0.628,1548,0.31,1549,0.664,1550,0.343,1551,0.405,1552,0.783,1553,0.405,1554,0.783,1555,1.472,1556,0.405,1557,0.783,1558,0.783,1559,1.337,1560,0.664,1561,0.783,1562,0.405,1563,0.405,1564,0.405,1565,0.405,1566,0.405,1567,0.405,1568,0.367,1569,0.405,1570,0.405,1571,0.325,1572,0.367,1573,0.343,1574,0.405,1575,0.405,1576,0.325,1577,0.405]],["component/12",[11,0.17,12,0.288]],["keyword/12",[]],["title/13-1",[392,40.617]],["name/13-1",[]],["text/13-1",[]],["component/13-1",[]],["keyword/13-1",[]],["title/13-2",[43,35.147,290,36.524]],["name/13-2",[]],["text/13-2",[]],["component/13-2",[]],["keyword/13-2",[]],["title/13-3",[95,30.021,587,40.941]],["name/13-3",[]],["text/13-3",[]],["component/13-3",[]],["keyword/13-3",[]],["title/13-4",[11,14.066,392,29.118,587,35.146]],["name/13-4",[]],["text/13-4",[]],["component/13-4",[]],["keyword/13-4",[]],["title/13-5",[11,12.322,95,22.576,561,30.012,587,30.788]],["name/13-5",[]],["text/13-5",[]],["component/13-5",[]],["keyword/13-5",[]],["title/13-6",[20,32.699,292,29.118,587,35.146]],["name/13-6",[]],["text/13-6",[]],["component/13-6",[]],["keyword/13-6",[]],["title/13-7",[9,15.178,11,10.963,36,23.093,44,22.314,51,21.608]],["name/13-7",[]],["text/13-7",[]],["component/13-7",[]],["keyword/13-7",[]],["title/13-8",[20,32.699,40,39.815,292,29.118]],["name/13-8",[]],["text/13-8",[]],["component/13-8",[]],["keyword/13-8",[]],["title/13-9",[9,15.178,51,21.608,706,24.436,1578,38.281,1579,35.728]],["name/13-9",[]],["text/13-9",[]],["component/13-9",[]],["keyword/13-9",[]],["title/13-10",[752,38.674]],["name/13-10",[]],["text/13-10",[]],["component/13-10",[]],["keyword/13-10",[]],["title/13-11",[1296,50.384]],["name/13-11",[]],["text/13-11",[]],["component/13-11",[]],["keyword/13-11",[]],["title/13",[11,16.386,587,40.941]],["name/13",[1580,0.861]],["text/13",[1,0.433,6,0.869,7,0.456,8,0.473,9,3.081,10,0.838,11,1.886,13,0.514,18,0.572,19,0.492,20,5.431,21,0.448,22,1.462,24,2.776,28,1.299,29,2.358,36,2.264,40,1.146,44,0.824,46,2.225,48,0.527,49,0.941,51,4.777,53,1.427,55,1.327,63,1.071,68,0.464,69,2.561,72,1.414,82,2.468,83,1.125,95,3.524,96,1.616,101,0.42,104,0.755,106,1.67,109,0.456,110,0.963,111,0.402,112,1.162,115,1.541,117,3.661,118,1.971,128,0.572,129,3.384,130,2.124,137,2.424,142,0.54,147,1.513,148,1.273,153,1.858,156,0.473,158,0.612,160,1.513,161,1.466,162,0.456,163,0.591,168,0.514,170,0.402,174,0.853,175,2.79,177,3.27,182,4.292,183,1.106,190,0.811,191,1.823,192,0.54,194,2.116,195,1.248,197,0.503,198,1.438,199,0.464,200,0.637,203,0.667,207,0.637,209,1.248,211,0.941,213,4.281,217,0.838,223,0.555,228,1.012,231,1.427,232,0.492,242,1.327,245,0.963,258,0.705,259,0.503,266,0.612,268,0.54,270,1.106,274,0.963,276,0.473,278,0.885,292,4.123,295,1.513,296,2.609,297,1.249,303,1.774,305,1.466,307,0.921,319,0.853,321,3.703,326,0.667,340,0.464,341,0.941,342,0.473,345,0.42,348,2.79,349,1.012,350,0.705,351,2.468,364,1.327,366,2.486,372,2.158,377,1.994,385,0.921,392,0.448,393,1.759,394,1.634,409,1.012,411,0.492,415,1.071,417,0.963,421,0.705,430,0.54,432,0.492,454,0.572,455,0.667,459,0.755,461,1.273,464,0.637,471,0.612,476,0.492,477,1.466,480,0.613,481,0.963,488,0.755,490,1.358,491,0.667,500,1.634,504,0.885,505,1.071,506,0.637,512,0.921,514,1.146,518,0.637,521,0.555,531,1.071,532,1.682,534,0.732,538,0.755,545,0.667,553,0.963,554,1.193,555,1.616,563,0.591,567,0.612,580,0.921,582,2.786,585,0.572,587,5.952,590,0.667,591,0.572,595,0.555,601,0.456,609,1.75,613,0.555,618,1.962,619,1.79,628,0.572,630,1.012,632,0.555,633,0.572,637,0.572,646,1.358,649,0.705,650,0.572,665,0.503,669,0.555,670,0.591,673,0.572,685,1.106,688,1.762,691,0.612,694,0.555,697,0.555,706,0.903,721,3.291,726,0.667,733,0.514,741,0.54,752,4.207,753,2.208,754,2.396,763,0.986,783,0.667,791,0.591,794,0.637,805,0.44,811,1.012,828,1.146,831,0.591,853,0.591,854,0.591,855,0.705,863,0.705,886,0.514,901,4.385,923,0.667,931,2.93,932,1.327,933,1.249,947,0.591,963,0.514,971,0.503,979,0.482,981,0.637,999,0.527,1007,0.54,1012,0.514,1020,0.572,1023,2.56,1024,3.925,1042,1.327,1056,0.527,1094,0.667,1099,1.012,1106,0.54,1121,1.75,1122,0.705,1123,0.705,1138,0.591,1140,1.969,1149,0.705,1150,0.705,1152,1.358,1160,0.941,1162,1.012,1163,0.612,1167,1.51,1175,0.555,1176,0.637,1177,1.9,1178,1.51,1184,0.637,1185,0.612,1186,0.637,1187,0.612,1188,0.591,1189,0.54,1190,0.572,1202,0.612,1203,1.616,1204,0.612,1205,0.637,1206,0.637,1209,0.612,1214,2.486,1221,0.591,1222,1.682,1225,1.193,1277,1.106,1296,0.555,1297,1.071,1324,1.04,1345,0.667,1356,1.249,1373,0.705,1380,0.705,1381,0.755,1455,0.755,1465,0.755,1479,0.755,1485,2.341,1490,0.667,1493,3.155,1500,1.249,1507,0.705,1532,2.786,1547,0.667,1548,2.116,1549,4.107,1578,4.092,1579,4.613,1581,0.755,1582,0.755,1583,1.32,1584,1.861,1585,1.249,1586,0.755,1587,0.832,1588,1.557,1589,0.832,1590,1.414,1591,0.705,1592,0.667,1593,1.557,1594,0.705,1595,0.832,1596,0.832,1597,0.832,1598,0.832,1599,0.832,1600,0.832,1601,0.832,1602,0.832,1603,0.832,1604,1.557,1605,0.832,1606,0.755,1607,1.557,1608,1.414,1609,0.591,1610,0.637,1611,0.705,1612,0.755,1613,0.705,1614,0.667,1615,0.755,1616,0.755,1617,0.755,1618,1.414,1619,0.832,1620,0.832,1621,0.832,1622,4.133,1623,2.196,1624,4.092,1625,1.557,1626,5.801,1627,0.832,1628,1.414,1629,1.414,1630,1.249,1631,0.755,1632,0.832,1633,0.832,1634,0.667,1635,0.705,1636,0.612,1637,0.832,1638,0.832,1639,0.637,1640,0.832,1641,0.832,1642,0.832,1643,0.832,1644,2.762,1645,0.755,1646,0.832,1647,2.968,1648,2.762,1649,2.762,1650,2.196,1651,0.832,1652,1.146,1653,2.196,1654,2.196,1655,0.832,1656,0.755,1657,0.832,1658,0.832,1659,0.832,1660,0.832,1661,0.832,1662,1.557,1663,1.994,1664,0.755,1665,0.637,1666,0.705,1667,0.755,1668,0.755,1669,1.557,1670,0.832,1671,0.832,1672,0.832,1673,2.508,1674,0.832,1675,0.832,1676,0.755,1677,1.557,1678,0.832,1679,0.832,1680,0.832,1681,0.832]],["component/13",[11,0.17,12,0.288]],["keyword/13",[]],["title/14-1",[43,35.147,290,36.524]],["name/14-1",[]],["text/14-1",[]],["component/14-1",[]],["keyword/14-1",[]],["title/14-2",[11,14.066,295,29.631,480,21.306]],["name/14-2",[]],["text/14-2",[]],["component/14-2",[]],["keyword/14-2",[]],["title/14-3",[82,23.093,111,20.366,137,19.814,162,23.093,345,21.278]],["name/14-3",[]],["text/14-3",[]],["component/14-3",[]],["keyword/14-3",[]],["title/14-4",[7,25.957,67,28.035,480,18.664,481,29.301]],["name/14-4",[]],["text/14-4",[]],["component/14-4",[]],["keyword/14-4",[]],["title/14-5",[7,29.631,27,36.12,480,21.306]],["name/14-5",[]],["text/14-5",[]],["component/14-5",[]],["keyword/14-5",[]],["title/14-6",[7,29.631,348,27.725,480,21.306]],["name/14-6",[]],["text/14-6",[]],["component/14-6",[]],["keyword/14-6",[]],["title/14-7",[11,12.322,172,23.561,480,18.664,561,30.012]],["name/14-7",[]],["text/14-7",[]],["component/14-7",[]],["keyword/14-7",[]],["title/14-8",[170,26.132,480,21.306,1042,32.699]],["name/14-8",[]],["text/14-8",[]],["component/14-8",[]],["keyword/14-8",[]],["title/14-9",[384,39.815,480,21.306,1042,32.699]],["name/14-9",[]],["text/14-9",[]],["component/14-9",[]],["keyword/14-9",[]],["title/14-10",[383,30.788,721,28.644,886,29.301,1682,38.015]],["name/14-10",[]],["text/14-10",[]],["component/14-10",[]],["keyword/14-10",[]],["title/14-11",[348,27.725,748,45.843,1682,43.396]],["name/14-11",[]],["text/14-11",[]],["component/14-11",[]],["keyword/14-11",[]],["title/14-12",[190,24.675,480,18.664,1166,36.303,1170,34.878]],["name/14-12",[]],["text/14-12",[]],["component/14-12",[]],["keyword/14-12",[]],["title/14-13",[260,29.631,418,25.771,1683,45.843]],["name/14-13",[]],["text/14-13",[]],["component/14-13",[]],["keyword/14-13",[]],["title/14-14",[102,43.338,1683,53.402]],["name/14-14",[]],["text/14-14",[]],["component/14-14",[]],["keyword/14-14",[]],["title/14-15",[190,28.168,198,28.168,480,21.306]],["name/14-15",[]],["text/14-15",[]],["component/14-15",[]],["keyword/14-15",[]],["title/14-16",[11,10.963,36,23.093,116,24.436,276,23.961,630,27.391]],["name/14-16",[]],["text/14-16",[]],["component/14-16",[]],["keyword/14-16",[]],["title/14-17",[139,25.081,162,25.957,345,23.916,480,18.664]],["name/14-17",[]],["text/14-17",[]],["component/14-17",[]],["keyword/14-17",[]],["title/14-18",[201,23.093,323,26.068,480,16.605,510,27.391,626,28.995]],["name/14-18",[]],["text/14-18",[]],["component/14-18",[]],["keyword/14-18",[]],["title/14-19",[11,9.873,116,22.008,167,19.164,217,20.439,480,14.955,667,19.772]],["name/14-19",[]],["text/14-19",[]],["component/14-19",[]],["keyword/14-19",[]],["title/14-20",[1296,50.384]],["name/14-20",[]],["text/14-20",[]],["component/14-20",[]],["keyword/14-20",[]],["title/14",[11,16.386,480,24.819]],["name/14",[1684,0.861]],["text/14",[1,0.829,3,0.244,6,0.385,7,3.276,8,3.106,9,2.55,10,0.542,11,1.874,12,0.157,13,0.427,15,0.272,17,0.508,19,0.409,20,0.418,21,1.516,22,1.71,24,1.397,25,0.244,27,0.237,28,1.101,29,0.828,36,0.195,44,0.188,46,1.63,48,0.225,49,0.79,51,2.555,53,0.655,55,1.28,63,0.244,67,0.409,68,0.888,69,1.475,73,0.252,76,0.237,79,0.323,81,0.301,82,0.872,83,3.756,86,0.252,89,0.923,91,0.272,95,1.72,98,0.272,101,0.179,102,0.244,106,1.703,109,1.421,110,0.22,111,1.655,112,0.985,113,1.095,114,1.172,115,0.888,116,4.028,117,0.349,118,0.533,123,0.596,125,0.449,126,0.808,127,0.225,129,2.8,131,0.301,137,2.515,138,0.285,139,2.705,142,1.376,145,0.252,147,1.16,148,0.923,149,0.237,150,0.79,152,0.301,153,2.25,154,2.118,156,1.058,158,0.742,159,0.4,160,2.075,162,1.659,163,0.716,165,0.693,166,1.559,167,0.66,168,0.427,170,2.889,172,1.506,173,1.008,174,1.294,175,3.206,176,0.449,177,2.408,180,0.449,181,0.22,185,0.508,186,0.237,190,2.845,191,2.3,194,2.153,195,0.202,196,0.262,197,0.215,198,2.457,199,0.729,200,0.272,201,2.259,209,1.058,211,0.215,213,2.759,217,2.304,218,0.202,219,0.984,225,0.323,227,3.541,230,0.301,232,2.709,233,1.699,234,0.693,235,0.272,239,0.655,242,0.418,243,0.623,245,0.623,252,0.301,253,0.508,255,0.231,256,0.638,259,0.609,260,2.731,264,0.623,266,0.742,268,0.655,271,0.85,274,0.808,275,2.169,276,1.475,278,1.721,279,0.285,283,0.596,285,0.244,288,0.584,292,0.191,293,0.285,294,0.237,295,0.195,296,0.191,300,0.609,307,0.409,308,0.529,309,0.252,310,0.418,315,0.769,317,0.638,319,0.378,321,1.873,322,0.182,323,0.22,332,0.272,337,0.244,340,0.562,341,0.609,342,1.204,345,4.009,346,0.655,348,0.516,349,1.376,351,1.421,358,1.151,362,0.508,364,1.28,366,0.461,372,1.504,379,0.272,380,2.624,382,0.301,383,3.476,384,2.073,385,0.21,393,1.63,394,0.596,404,2.304,405,0.915,409,0.231,411,0.21,413,0.673,418,1.125,430,0.449,432,0.596,433,0.693,436,1.151,441,0.962,461,0.758,464,0.272,468,0.262,471,0.262,472,0.285,473,0.252,475,0.237,476,0.409,478,1.31,480,4.039,481,1.31,490,1.605,491,0.554,492,0.262,499,0.461,500,0.596,504,1.204,505,0.693,506,0.272,510,3.758,512,1.666,513,0.262,520,0.231,521,0.873,523,0.231,525,0.638,530,1.179,531,0.244,532,2.475,534,1.324,535,3.321,536,0.244,537,0.206,541,1.505,549,0.252,551,2.647,553,0.984,555,0.262,559,0.285,561,0.828,564,0.262,568,0.529,574,0.225,576,0.716,580,1.253,581,0.237,582,0.808,583,0.272,584,0.554,585,0.475,587,0.231,591,0.693,593,0.693,595,0.237,596,0.638,598,0.673,601,1.16,609,1.008,619,0.716,622,0.461,624,1.623,625,0.22,626,2.606,630,0.231,637,0.899,639,0.627,641,0.449,643,0.262,644,0.899,648,1.342,649,0.301,654,0.252,661,0.508,662,0.237,664,0.529,665,0.418,667,0.36,673,0.475,678,0.873,679,0.272,680,1.426,685,0.252,686,0.461,694,0.237,703,0.873,705,0.285,721,1.428,722,0.461,724,0.301,728,1.784,730,0.742,731,0.475,732,0.461,733,1.605,735,0.262,741,0.231,747,0.427,748,0.301,752,0.67,753,0.174,754,2.295,755,0.231,756,0.272,757,0.323,758,0.285,762,0.301,763,0.225,768,0.449,776,0.673,779,0.237,780,0.301,781,0.285,782,0.301,788,0.262,790,0.285,791,0.252,794,0.272,796,0.323,799,0.252,802,0.225,806,0.21,825,0.508,827,0.272,829,0.262,830,0.202,848,1.426,851,0.252,853,0.252,854,0.929,862,0.262,866,1.002,886,2.233,895,0.285,900,0.215,901,4.651,905,0.742,925,0.252,931,3.672,939,0.285,949,0.237,954,0.427,955,3.329,957,0.491,962,0.491,963,0.623,967,0.244,974,0.22,979,0.758,983,0.673,999,0.437,1003,0.225,1005,0.285,1007,0.449,1012,0.427,1017,0.244,1024,0.244,1034,1.605,1042,4.689,1056,0.225,1077,0.673,1078,0.301,1080,0.873,1081,0.285,1084,0.272,1086,0.716,1095,0.272,1096,0.475,1097,0.301,1099,0.655,1101,0.301,1103,0.962,1105,0.301,1106,0.231,1107,0.285,1113,0.262,1114,0.262,1118,0.272,1121,1.008,1138,0.252,1139,0.262,1140,2.603,1152,3.379,1156,0.301,1157,0.301,1158,0.285,1159,0.285,1160,2.393,1161,2.753,1162,2.573,1164,0.285,1165,0.301,1166,0.529,1167,1.095,1170,1.172,1172,0.929,1175,1.414,1177,0.899,1178,0.899,1179,0.272,1181,0.272,1182,0.244,1183,0.252,1185,0.262,1186,0.272,1187,0.508,1188,0.252,1189,0.231,1190,0.244,1194,0.285,1196,0.529,1202,0.742,1203,0.262,1204,0.262,1205,0.272,1209,0.262,1219,0.285,1221,0.252,1223,1.108,1228,0.301,1229,0.854,1231,0.301,1260,0.301,1267,0.285,1269,0.285,1272,0.554,1277,0.929,1294,0.554,1295,0.585,1297,0.475,1299,0.427,1302,0.301,1309,0.772,1312,0.285,1314,0.285,1315,0.301,1316,1.22,1320,0.301,1321,0.272,1322,0.262,1324,0.237,1341,0.285,1344,0.529,1348,0.285,1349,0.301,1362,0.323,1365,1.923,1371,0.508,1374,0.272,1389,0.285,1397,1.172,1401,0.508,1409,0.323,1431,0.301,1434,0.585,1466,0.808,1473,0.272,1474,0.323,1475,0.554,1476,0.323,1484,0.585,1487,0.627,1532,0.808,1534,0.808,1540,0.301,1545,0.301,1547,0.285,1548,0.772,1550,0.301,1560,0.301,1572,0.323,1582,0.323,1583,0.301,1584,0.301,1590,0.323,1591,0.301,1592,0.285,1609,0.252,1610,0.529,1614,0.808,1615,0.323,1616,0.323,1617,0.323,1618,0.627,1624,0.323,1630,0.285,1634,0.285,1636,1.172,1639,1.002,1647,0.627,1652,0.262,1656,0.627,1665,0.272,1667,0.323,1682,0.554,1683,5.149,1685,0.627,1686,1.008,1687,0.301,1688,0.627,1689,0.554,1690,0.323,1691,0.323,1692,0.355,1693,0.285,1694,0.691,1695,0.355,1696,0.627,1697,1.008,1698,0.355,1699,0.285,1700,0.301,1701,2.789,1702,0.285,1703,0.627,1704,0.355,1705,0.355,1706,0.691,1707,0.691,1708,0.808,1709,0.301,1710,0.355,1711,0.355,1712,0.355,1713,0.272,1714,0.355,1715,0.355,1716,0.355,1717,0.355,1718,0.323,1719,0.854,1720,0.272,1721,0.323,1722,0.355,1723,1.81,1724,0.355,1725,1.008,1726,1.307,1727,1.307,1728,1.349,1729,2.595,1730,0.627,1731,0.585,1732,0.355,1733,0.355,1734,0.272,1735,0.355,1736,0.355,1737,0.355,1738,0.355,1739,0.355,1740,0.355,1741,1.307,1742,0.915,1743,1.108,1744,0.301,1745,0.323,1746,0.323,1747,0.355,1748,0.355,1749,0.323,1750,0.323,1751,0.355,1752,0.355,1753,0.355,1754,0.355,1755,0.529,1756,0.355,1757,1.008,1758,0.355,1759,0.323,1760,0.355,1761,0.627,1762,0.627,1763,0.691,1764,1.008,1765,0.554,1766,1.307,1767,0.585,1768,1.008,1769,0.691,1770,0.691,1771,1.008,1772,0.529,1773,0.691,1774,1.307,1775,0.355,1776,0.355,1777,0.355,1778,0.355,1779,0.355,1780,0.355,1781,0.285,1782,0.355,1783,0.355,1784,0.355,1785,0.355,1786,1.895,1787,0.355,1788,0.301,1789,0.355,1790,0.323,1791,0.355,1792,0.355,1793,0.285,1794,0.691,1795,0.355,1796,0.355,1797,0.854,1798,0.691,1799,0.355,1800,1.002,1801,0.323,1802,0.355,1803,0.355,1804,0.355,1805,1.349,1806,0.355,1807,0.355,1808,0.355,1809,0.355,1810,0.355,1811,0.355,1812,0.272,1813,0.355,1814,0.301,1815,0.323,1816,0.355,1817,0.323,1818,0.691,1819,0.627,1820,0.355,1821,0.355,1822,0.355,1823,0.627,1824,0.301,1825,0.355,1826,0.355,1827,0.323,1828,0.323,1829,0.355,1830,0.323,1831,0.355,1832,0.301,1833,0.301,1834,0.285,1835,0.355,1836,0.554,1837,0.355,1838,0.355,1839,0.355,1840,0.355,1841,0.355,1842,0.355,1843,0.355,1844,0.355,1845,0.355,1846,0.355,1847,0.554,1848,0.355,1849,0.355,1850,0.355,1851,0.355,1852,0.355,1853,0.355,1854,0.355,1855,0.355,1856,0.323,1857,2.002,1858,0.691,1859,0.323,1860,0.691,1861,1.008,1862,0.323,1863,1.187,1864,0.301,1865,1.307,1866,0.355,1867,0.301,1868,1.307,1869,0.691,1870,0.691,1871,0.355,1872,0.691,1873,0.355,1874,0.691,1875,0.355,1876,1.307,1877,0.627,1878,0.355,1879,0.323,1880,0.323,1881,0.301,1882,0.529,1883,0.323,1884,0.323,1885,0.323,1886,1.008,1887,0.355,1888,0.808,1889,0.355,1890,0.285,1891,0.323,1892,0.355,1893,0.627,1894,0.301,1895,0.323,1896,0.301,1897,0.355,1898,0.808,1899,0.262,1900,0.301]],["component/14",[11,0.17,12,0.288]],["keyword/14",[]],["title/15-1",[392,40.617]],["name/15-1",[]],["text/15-1",[]],["component/15-1",[]],["keyword/15-1",[]],["title/15-2",[43,35.147,290,36.524]],["name/15-2",[]],["text/15-2",[]],["component/15-2",[]],["keyword/15-2",[]],["title/15-3",[0,34.26,11,14.066,306,28.168]],["name/15-3",[]],["text/15-3",[]],["component/15-3",[]],["keyword/15-3",[]],["title/15-4",[172,31.331,967,43.338]],["name/15-4",[]],["text/15-4",[]],["component/15-4",[]],["keyword/15-4",[]],["title/15-5",[306,32.812,315,30.441]],["name/15-5",[]],["text/15-5",[]],["component/15-5",[]],["keyword/15-5",[]],["title/15-6",[306,28.168,315,26.132,348,27.725]],["name/15-6",[]],["text/15-6",[]],["component/15-6",[]],["keyword/15-6",[]],["title/15-7",[315,30.441,1702,50.551]],["name/15-7",[]],["text/15-7",[]],["component/15-7",[]],["keyword/15-7",[]],["title/15-8",[315,30.441,583,48.275]],["name/15-8",[]],["text/15-8",[]],["component/15-8",[]],["keyword/15-8",[]],["title/15-9",[315,30.441,957,44.757]],["name/15-9",[]],["text/15-9",[]],["component/15-9",[]],["keyword/15-9",[]],["title/15-10",[306,28.168,315,26.132,581,36.12]],["name/15-10",[]],["text/15-10",[]],["component/15-10",[]],["keyword/15-10",[]],["title/15-11",[213,30.745,278,30.745,319,29.631]],["name/15-11",[]],["text/15-11",[]],["component/15-11",[]],["keyword/15-11",[]],["title/15-12",[306,28.168,315,26.132,500,32.003]],["name/15-12",[]],["text/15-12",[]],["component/15-12",[]],["keyword/15-12",[]],["title/15-13",[170,22.891,306,24.675,315,22.891,581,31.641]],["name/15-13",[]],["text/15-13",[]],["component/15-13",[]],["keyword/15-13",[]],["title/15-14",[209,35.814,1901,57.218]],["name/15-14",[]],["text/15-14",[]],["component/15-14",[]],["keyword/15-14",[]],["title/15-15",[1140,30.021,1902,57.218]],["name/15-15",[]],["text/15-15",[]],["component/15-15",[]],["keyword/15-15",[]],["title/15-16",[1140,30.021,1903,57.218]],["name/15-16",[]],["text/15-16",[]],["component/15-16",[]],["keyword/15-16",[]],["title/15-17",[170,22.891,306,24.675,315,22.891,830,26.933]],["name/15-17",[]],["text/15-17",[]],["component/15-17",[]],["keyword/15-17",[]],["title/15-18",[170,26.132,306,28.168,315,26.132]],["name/15-18",[]],["text/15-18",[]],["component/15-18",[]],["keyword/15-18",[]],["title/15-19",[22,33.352,536,43.338]],["name/15-19",[]],["text/15-19",[]],["component/15-19",[]],["keyword/15-19",[]],["title/15-20",[198,28.168,306,28.168,315,26.132]],["name/15-20",[]],["text/15-20",[]],["component/15-20",[]],["keyword/15-20",[]],["title/15-21",[480,21.306,651,38.422,1904,49.119]],["name/15-21",[]],["text/15-21",[]],["component/15-21",[]],["keyword/15-21",[]],["title/15-22",[167,21.278,306,21.953,315,20.366,317,26.701,667,21.953]],["name/15-22",[]],["text/15-22",[]],["component/15-22",[]],["keyword/15-22",[]],["title/15-23",[1296,50.384]],["name/15-23",[]],["text/15-23",[]],["component/15-23",[]],["keyword/15-23",[]],["title/15",[11,16.386,306,32.812]],["name/15",[1905,0.861]],["text/15",[0,0.916,1,0.753,6,2.248,7,0.55,8,0.297,9,2.132,10,0.999,11,1.871,21,1.569,22,1.183,24,1.203,25,0.69,27,0.349,28,1.724,29,0.636,35,0.911,36,0.287,40,0.385,44,0.531,46,1.569,48,0.331,49,1.351,51,1.145,55,4.108,67,0.31,68,1.797,69,1.657,76,0.349,82,0.287,83,2.794,89,0.582,91,1.108,92,0.911,95,1.92,101,0.507,102,0.36,106,2.437,107,1.226,111,3.057,112,0.982,113,0.36,115,1.246,123,0.31,126,2.169,127,0.916,128,0.36,129,2.578,130,0.34,137,0.246,139,1.369,142,1.68,146,0.42,147,0.792,148,1.075,150,2.121,151,0.349,153,0.297,156,1.47,157,0.444,159,0.303,160,2.069,161,0.349,162,0.792,165,0.69,167,1.128,170,3.334,172,0.922,173,0.331,174,0.287,177,2.274,181,0.324,182,0.324,190,1.827,191,1.626,194,1.657,195,1.27,197,0.316,198,0.753,199,2.738,201,0.792,205,0.769,209,1.47,211,1.351,213,3.446,217,0.779,219,0.621,232,0.31,235,0.401,237,0.582,238,0.769,239,1.205,242,1.351,243,1.992,245,1.381,246,1.351,250,1.16,251,1.226,252,1.226,255,0.34,256,0.916,259,0.874,264,0.621,265,0.324,268,0.34,271,0.94,274,0.894,276,0.297,278,0.822,283,0.31,288,0.582,290,0.303,292,0.282,294,0.349,300,0.316,305,0.67,306,5.514,310,0.607,315,4.977,317,1.846,319,0.792,321,1.689,325,1.421,333,0.713,340,0.292,341,0.607,345,1.907,346,0.34,348,2.613,349,1.68,351,2.339,356,0.805,364,1.351,383,0.94,385,1.322,392,0.282,393,1.203,394,0.31,400,0.385,404,1.392,409,1.205,411,0.856,413,0.966,415,0.36,417,0.894,418,0.478,424,0.67,432,0.856,436,1.381,437,0.349,441,0.607,454,0.36,461,0.582,470,0.636,471,0.385,473,0.372,474,0.331,477,0.349,478,2.336,479,0.594,480,1.269,481,0.324,495,0.652,499,0.349,500,1.53,501,0.444,504,1.054,510,0.34,512,0.31,515,0.331,516,0.331,520,0.34,521,0.349,525,1.175,526,0.34,530,0.331,534,1.649,535,0.34,536,1.276,537,0.303,541,0.713,551,1.381,552,0.769,553,0.621,555,0.385,557,2.151,561,0.331,564,0.385,576,1.027,580,3.017,581,2.151,582,0.324,583,4.944,585,0.995,586,0.851,593,1.537,595,0.67,596,0.636,601,1.922,609,0.636,619,1.417,622,2.151,625,1.599,626,0.36,628,0.36,630,0.34,632,0.349,633,0.36,635,1.792,637,0.36,640,0.42,641,0.34,643,0.385,644,0.69,646,0.324,651,3.192,654,0.372,657,0.42,665,0.874,666,0.69,667,1.163,673,0.36,678,1.239,680,1.108,691,0.739,706,0.303,714,0.401,718,1.313,721,0.316,722,0.349,728,0.636,732,0.349,733,2.336,734,1.421,735,0.739,741,0.34,747,0.621,751,0.444,752,1.936,754,1.075,755,0.34,764,0.349,765,0.444,768,2.455,772,0.769,775,1.904,781,0.42,782,0.444,783,0.805,785,0.444,786,0.475,792,0.67,795,0.851,805,0.277,806,0.31,811,0.34,825,0.385,828,0.385,829,0.739,830,0.571,831,0.713,835,0.636,854,0.713,862,0.739,886,0.324,900,0.316,901,5.375,925,0.372,931,3.579,933,0.42,944,0.385,947,0.372,954,0.621,955,0.349,957,3.989,959,1.727,962,0.713,963,1.599,966,0.805,967,0.36,971,0.607,983,0.67,990,0.36,999,0.331,1023,0.995,1034,0.621,1042,0.316,1056,0.331,1071,0.385,1080,0.966,1082,0.805,1085,0.42,1103,0.739,1106,0.34,1108,2.584,1109,0.444,1110,0.444,1121,1.638,1124,0.36,1139,0.385,1140,2.242,1152,3.569,1160,3.489,1161,1.727,1162,1.205,1167,0.69,1172,0.372,1182,0.36,1183,1.027,1189,0.94,1196,0.401,1214,0.67,1218,0.444,1222,0.769,1267,0.805,1277,0.372,1297,1.276,1300,0.444,1308,1.226,1309,0.401,1314,2.815,1321,0.401,1344,0.769,1348,0.42,1356,0.42,1371,0.385,1374,0.401,1377,0.475,1393,0.475,1403,0.401,1432,0.475,1433,0.475,1436,0.911,1460,0.475,1466,0.805,1467,0.851,1470,0.401,1473,0.401,1477,0.851,1489,0.42,1500,0.42,1507,0.444,1510,0.475,1538,0.401,1550,0.851,1610,0.401,1613,0.851,1639,1.982,1652,0.385,1693,0.42,1701,0.385,1702,4.864,1723,0.401,1728,0.444,1734,0.401,1743,0.444,1749,0.475,1761,0.475,1765,2.338,1786,1.16,1812,0.769,1814,0.444,1833,0.444,1834,0.42,1867,0.851,1881,0.444,1882,0.401,1896,0.444,1898,0.42,1901,2.925,1902,4.63,1903,3.877,1904,4.081,1906,1.446,1907,1.855,1908,0.523,1909,1.685,1910,0.523,1911,1.004,1912,3.778,1913,2.234,1914,2.234,1915,1.446,1916,0.475,1917,1.108,1918,0.523,1919,0.523,1920,0.523,1921,1.004,1922,0.475,1923,0.523,1924,0.523,1925,0.475,1926,0.401,1927,1.004,1928,0.523,1929,0.523,1930,0.911,1931,0.523,1932,0.523,1933,0.523,1934,0.523,1935,0.523,1936,0.523,1937,0.851,1938,3.416,1939,0.911,1940,1.004,1941,0.475,1942,2.234,1943,0.475,1944,0.475,1945,0.911,1946,0.523,1947,1.446,1948,0.523,1949,0.523,1950,0.444,1951,0.523,1952,1.004,1953,1.004,1954,0.523,1955,0.523,1956,1.004,1957,0.523,1958,0.475,1959,0.475,1960,0.475,1961,0.523,1962,0.523,1963,1.004,1964,0.444,1965,0.523,1966,0.523,1967,0.523,1968,0.523,1969,0.523,1970,0.523,1971,1.446,1972,1.446,1973,1.446,1974,0.523,1975,0.523,1976,1.004,1977,1.004,1978,0.523,1979,0.523,1980,0.42,1981,0.523,1982,0.475,1983,0.523,1984,0.523,1985,0.523,1986,0.523,1987,1.446,1988,0.523,1989,0.444,1990,1.446,1991,0.523,1992,1.004,1993,1.446,1994,0.523,1995,0.42,1996,1.446,1997,1.446,1998,1.313,1999,0.523,2000,1.004,2001,0.523,2002,0.523,2003,1.446,2004,0.523,2005,0.523,2006,0.523,2007,0.523,2008,0.523,2009,0.523,2010,0.523,2011,0.523,2012,0.523,2013,0.523,2014,0.523,2015,0.523,2016,0.911,2017,0.523,2018,0.523,2019,0.523,2020,0.523,2021,0.523,2022,0.523,2023,1.004,2024,0.523,2025,0.523,2026,0.523,2027,1.446,2028,1.004,2029,0.385,2030,0.475,2031,1.855,2032,1.004,2033,1.446,2034,1.004,2035,0.523,2036,0.523,2037,0.475,2038,0.523,2039,0.523,2040,0.523,2041,1.004,2042,1.004,2043,1.004,2044,0.444,2045,1.004,2046,0.523,2047,0.523,2048,0.475,2049,0.523,2050,1.226,2051,0.805,2052,0.523,2053,0.475,2054,0.523,2055,0.523,2056,0.523,2057,0.523,2058,0.523,2059,0.475]],["component/15",[11,0.17,12,0.288]],["keyword/15",[]],["title/16-1",[392,40.617]],["name/16-1",[]],["text/16-1",[]],["component/16-1",[]],["keyword/16-1",[]],["title/16-2",[43,35.147,290,36.524]],["name/16-2",[]],["text/16-2",[]],["component/16-2",[]],["keyword/16-2",[]],["title/16-3",[11,14.066,122,28.631,392,29.118]],["name/16-3",[]],["text/16-3",[]],["component/16-3",[]],["keyword/16-3",[]],["title/16-4",[111,26.132,122,28.631,393,29.118]],["name/16-4",[]],["text/16-4",[]],["component/16-4",[]],["keyword/16-4",[]],["title/16-5",[122,33.352,348,32.297]],["name/16-5",[]],["text/16-5",[]],["component/16-5",[]],["keyword/16-5",[]],["title/16-6",[11,14.066,122,28.631,534,25.423]],["name/16-6",[]],["text/16-6",[]],["component/16-6",[]],["keyword/16-6",[]],["title/16-7",[122,33.352,752,32.297]],["name/16-7",[]],["text/16-7",[]],["component/16-7",[]],["keyword/16-7",[]],["title/16-8",[122,33.352,565,50.551]],["name/16-8",[]],["text/16-8",[]],["component/16-8",[]],["keyword/16-8",[]],["title/16-9",[122,33.352,2060,53.402]],["name/16-9",[]],["text/16-9",[]],["component/16-9",[]],["keyword/16-9",[]],["title/16-10",[122,28.631,348,27.725,2060,45.843]],["name/16-10",[]],["text/16-10",[]],["component/16-10",[]],["keyword/16-10",[]],["title/16-11",[11,12.322,111,22.891,122,25.081,2061,33.658]],["name/16-11",[]],["text/16-11",[]],["component/16-11",[]],["keyword/16-11",[]],["title/16-12",[9,15.178,213,23.961,244,28.995,246,37.897]],["name/16-12",[]],["text/16-12",[]],["component/16-12",[]],["keyword/16-12",[]],["title/16-13",[9,15.178,244,28.995,650,28.995,1124,28.995,2062,38.281]],["name/16-13",[]],["text/16-13",[]],["component/16-13",[]],["keyword/16-13",[]],["title/16-14",[122,28.631,260,29.631,418,25.771]],["name/16-14",[]],["text/16-14",[]],["component/16-14",[]],["keyword/16-14",[]],["title/16-15",[1296,50.384]],["name/16-15",[]],["text/16-15",[]],["component/16-15",[]],["keyword/16-15",[]],["title/16",[11,16.386,122,33.352]],["name/16",[2063,0.861]],["text/16",[0,0.436,1,1.462,6,1.566,7,0.377,8,0.74,9,2.634,11,2.014,19,1.097,21,1.511,22,0.364,24,1.938,25,0.896,29,1.175,40,0.506,43,0.726,44,0.689,46,0.998,48,0.825,51,1.845,53,0.447,55,0.416,62,0.447,63,0.896,66,1.205,67,0.407,68,0.384,69,2.426,73,1.317,82,0.377,83,4.018,95,3.428,101,0.657,102,1.619,106,0.416,109,0.713,110,0.425,111,3.476,112,2.26,115,2.701,118,0.364,122,5.658,123,1.393,124,0.805,125,0.447,129,3.466,130,0.846,132,0.506,137,2.509,139,0.982,142,0.447,143,1.672,147,1.29,148,1.364,150,1.423,151,0.459,152,0.583,154,0.805,156,1.338,159,0.399,160,0.713,162,2.163,163,0.925,164,0.959,165,1.619,167,0.347,168,0.425,170,1.739,174,1.538,175,2.188,176,1.53,177,1.739,178,0.552,179,0.552,180,0.447,181,0.425,182,1.992,185,0.959,190,2.223,191,1.313,192,0.846,194,2.244,195,0.74,198,1.226,199,1.566,203,0.552,205,0.527,213,2.752,217,0.37,219,1.147,221,0.896,224,2.371,228,0.447,234,0.473,237,0.755,239,0.447,242,0.416,243,4.905,244,3.825,245,0.425,246,4.995,255,2.339,260,2.652,264,0.425,265,0.425,268,0.447,270,0.489,271,1.205,272,0.527,275,0.436,276,1.596,278,1.338,282,0.552,292,0.37,294,0.459,295,1.538,298,2.151,306,2.378,310,0.787,314,1.104,315,1.356,316,0.527,317,0.825,321,2.288,325,1.421,330,0.552,332,0.998,336,1.572,337,0.473,339,0.583,340,0.384,341,0.787,345,0.657,346,0.447,348,2.613,350,0.583,351,1.538,358,0.805,360,0.489,364,1.121,366,0.87,372,1.364,382,1.104,383,3.468,385,2.865,392,0.37,393,0.998,394,0.771,404,1.938,408,0.527,413,1.572,414,0.527,418,2.034,427,3.142,430,0.447,431,0.527,432,0.407,436,2.441,437,0.459,438,0.552,441,0.416,461,1.075,470,0.436,474,0.436,476,0.407,478,4.524,481,1.736,490,0.805,496,0.527,499,0.459,500,1.906,504,1.054,505,0.473,510,0.846,511,0.625,512,0.771,513,0.506,520,0.846,522,0.552,523,0.447,525,0.436,530,0.436,534,1.514,537,0.399,545,0.552,546,0.583,551,0.425,552,0.527,557,1.572,563,0.489,565,0.552,567,0.506,568,0.527,580,1.393,581,4.109,582,0.425,584,1.045,591,0.473,595,0.87,596,1.778,601,1.29,604,0.959,609,0.825,610,0.527,614,0.506,619,1.764,622,0.459,625,0.425,626,0.896,637,0.896,641,1.824,643,0.959,644,0.473,646,0.425,650,3.671,661,0.506,662,1.239,665,1.121,666,0.473,669,0.87,670,0.925,673,0.473,678,1.239,679,0.527,680,0.998,689,0.583,692,0.583,694,0.459,697,0.87,713,0.583,714,0.527,717,0.998,719,0.896,721,0.416,722,0.87,725,0.552,728,0.436,730,0.506,731,2.715,735,0.506,752,2.613,753,0.337,754,2.647,763,0.436,764,1.239,766,3.051,767,1.183,768,0.846,779,0.87,788,1.365,791,1.317,792,0.87,800,1.889,805,0.364,806,0.407,811,0.447,819,0.959,822,0.527,827,0.527,828,0.506,829,1.733,831,0.925,860,0.489,866,0.527,869,0.506,900,0.416,901,4.907,925,0.489,931,1.697,949,0.459,954,0.425,955,0.87,958,0.527,959,1.239,970,0.583,971,0.416,974,1.456,979,0.399,981,0.527,984,0.552,990,0.896,1003,0.436,1008,1.104,1017,0.896,1034,0.425,1056,0.436,1082,1.045,1084,0.527,1087,0.583,1091,1.995,1096,0.896,1099,0.447,1106,0.447,1108,0.552,1111,0.583,1124,1.619,1137,0.527,1140,1.122,1152,0.805,1160,1.947,1167,0.473,1175,1.239,1177,1.276,1178,1.276,1188,0.489,1189,0.447,1202,0.506,1204,0.506,1209,0.506,1214,1.239,1215,0.959,1221,0.925,1222,1.803,1225,0.527,1268,1.045,1299,0.425,1322,0.506,1324,1.239,1349,0.583,1360,0.625,1361,0.583,1368,3.709,1370,1.183,1371,0.506,1389,1.045,1403,3.906,1408,0.583,1468,0.625,1470,0.527,1490,1.488,1534,0.552,1547,0.552,1576,0.552,1581,0.625,1584,0.583,1586,0.625,1606,1.183,1610,0.998,1613,0.583,1639,0.998,1652,0.959,1666,0.583,1673,1.183,1689,0.552,1691,0.625,1708,0.552,1750,0.625,1772,0.527,1793,0.552,1812,0.998,1847,1.045,1867,1.572,1879,0.625,1882,0.527,1916,0.625,1917,0.527,1937,0.583,1938,1.104,1939,0.625,1941,0.625,1943,0.625,1944,0.625,1950,0.583,1964,0.583,1989,0.583,1995,0.552,2016,0.625,2048,0.625,2050,0.583,2060,1.572,2061,1.672,2062,3.585,2064,1.302,2065,1.684,2066,0.625,2067,1.995,2068,0.688,2069,0.583,2070,1.104,2071,0.688,2072,0.552,2073,0.688,2074,1.045,2075,0.688,2076,3.948,2077,4.842,2078,2.807,2079,3.221,2080,0.625,2081,1.302,2082,1.302,2083,1.302,2084,0.688,2085,0.583,2086,0.625,2087,0.688,2088,0.625,2089,0.688,2090,0.688,2091,0.688,2092,0.625,2093,0.688,2094,0.688,2095,0.688,2096,0.688,2097,0.688,2098,0.688,2099,0.688,2100,0.688,2101,1.302,2102,1.183,2103,0.688,2104,1.302,2105,0.688,2106,0.688,2107,0.688,2108,0.688,2109,0.688,2110,0.688,2111,0.688,2112,0.688,2113,0.688,2114,0.688,2115,0.688,2116,0.688,2117,0.688,2118,1.302,2119,0.688,2120,1.302,2121,1.855,2122,0.688,2123,0.625,2124,0.688,2125,0.583,2126,1.302,2127,1.302,2128,0.688,2129,0.688,2130,0.688,2131,0.688,2132,0.688,2133,1.302,2134,0.688,2135,0.625,2136,0.688,2137,0.688,2138,0.688,2139,1.855,2140,0.688,2141,0.688,2142,0.688,2143,0.688,2144,0.688,2145,0.688,2146,0.688]],["component/16",[11,0.17,12,0.288]],["keyword/16",[]],["title/17-1",[43,35.147,290,36.524]],["name/17-1",[]],["text/17-1",[]],["component/17-1",[]],["keyword/17-1",[]],["title/17-2",[120,33.352,121,33.352]],["name/17-2",[]],["text/17-2",[]],["component/17-2",[]],["keyword/17-2",[]],["title/17-3",[120,22.314,121,22.314,172,20.962,259,25.484,323,26.068]],["name/17-3",[]],["text/17-3",[]],["component/17-3",[]],["keyword/17-3",[]],["title/17-4",[237,36.524,613,42.076]],["name/17-4",[]],["text/17-4",[]],["component/17-4",[]],["keyword/17-4",[]],["title/17-5",[573,47.789]],["name/17-5",[]],["text/17-5",[]],["component/17-5",[]],["keyword/17-5",[]],["title/17-6",[572,47.789]],["name/17-6",[]],["text/17-6",[]],["component/17-6",[]],["keyword/17-6",[]],["title/17-7",[571,47.789]],["name/17-7",[]],["text/17-7",[]],["component/17-7",[]],["keyword/17-7",[]],["title/17-8",[574,47.789]],["name/17-8",[]],["text/17-8",[]],["component/17-8",[]],["keyword/17-8",[]],["title/17-9",[569,40.941,570,40.941]],["name/17-9",[]],["text/17-9",[]],["component/17-9",[]],["keyword/17-9",[]],["title/17-10",[51,27.725,120,28.631,121,28.631]],["name/17-10",[]],["text/17-10",[]],["component/17-10",[]],["keyword/17-10",[]],["title/17-11",[51,21.608,120,22.314,121,22.314,474,26.701,534,19.814]],["name/17-11",[]],["text/17-11",[]],["component/17-11",[]],["keyword/17-11",[]],["title/17-12",[11,12.322,120,25.081,121,25.081,752,24.287]],["name/17-12",[]],["text/17-12",[]],["component/17-12",[]],["keyword/17-12",[]],["title/17-13",[372,36.524,752,32.297]],["name/17-13",[]],["text/17-13",[]],["component/17-13",[]],["keyword/17-13",[]],["title/17-14",[170,22.891,480,18.664,572,30.012,2029,34.878]],["name/17-14",[]],["text/17-14",[]],["component/17-14",[]],["keyword/17-14",[]],["title/17-15",[308,36.303,572,30.012,1140,22.576,1800,36.303]],["name/17-15",[]],["text/17-15",[]],["component/17-15",[]],["keyword/17-15",[]],["title/17-16",[351,29.631,1140,25.771,1805,45.843]],["name/17-16",[]],["text/17-16",[]],["component/17-16",[]],["keyword/17-16",[]],["title/17-17",[76,25.354,201,20.799,260,20.799,418,18.089,479,22.464,1140,18.089]],["name/17-17",[]],["text/17-17",[]],["component/17-17",[]],["keyword/17-17",[]],["title/17-18",[295,29.631,572,34.26,1532,33.448]],["name/17-18",[]],["text/17-18",[]],["component/17-18",[]],["keyword/17-18",[]],["title/17-19",[11,12.322,116,27.466,232,28.035,480,18.664]],["name/17-19",[]],["text/17-19",[]],["component/17-19",[]],["keyword/17-19",[]],["title/17-20",[167,23.916,572,30.012,667,24.675,2029,34.878]],["name/17-20",[]],["text/17-20",[]],["component/17-20",[]],["keyword/17-20",[]],["title/17-21",[139,25.081,479,28.035,516,30.012,2147,38.015]],["name/17-21",[]],["text/17-21",[]],["component/17-21",[]],["keyword/17-21",[]],["title/17-22",[479,32.003,772,41.442,2147,43.396]],["name/17-22",[]],["text/17-22",[]],["component/17-22",[]],["keyword/17-22",[]],["title/17-23",[256,30.012,323,29.301,479,28.035,2147,38.015]],["name/17-23",[]],["text/17-23",[]],["component/17-23",[]],["keyword/17-23",[]],["title/17-24",[480,21.306,569,35.146,570,35.146]],["name/17-24",[]],["text/17-24",[]],["component/17-24",[]],["keyword/17-24",[]],["title/17-25",[569,35.146,570,35.146,1532,33.448]],["name/17-25",[]],["text/17-25",[]],["component/17-25",[]],["keyword/17-25",[]],["title/17-26",[232,28.035,480,18.664,569,30.788,570,30.788]],["name/17-26",[]],["text/17-26",[]],["component/17-26",[]],["keyword/17-26",[]],["title/17-27",[167,23.916,569,30.788,570,30.788,667,24.675]],["name/17-27",[]],["text/17-27",[]],["component/17-27",[]],["keyword/17-27",[]],["title/17-28",[9,19.475,613,36.12,752,27.725]],["name/17-28",[]],["text/17-28",[]],["component/17-28",[]],["keyword/17-28",[]],["title/17-29",[9,22.686,613,42.076]],["name/17-29",[]],["text/17-29",[]],["component/17-29",[]],["keyword/17-29",[]],["title/17-30",[170,36.451]],["name/17-30",[]],["text/17-30",[]],["component/17-30",[]],["keyword/17-30",[]],["title/17-31",[232,32.003,480,21.306,573,34.26]],["name/17-31",[]],["text/17-31",[]],["component/17-31",[]],["keyword/17-31",[]],["title/17-32",[9,17.06,167,23.916,613,31.641,667,24.675]],["name/17-32",[]],["text/17-32",[]],["component/17-32",[]],["keyword/17-32",[]],["title/17-33",[120,22.314,121,22.314,237,24.436,323,26.068,613,28.151]],["name/17-33",[]],["text/17-33",[]],["component/17-33",[]],["keyword/17-33",[]],["title/17-34",[404,33.92,573,39.909]],["name/17-34",[]],["text/17-34",[]],["component/17-34",[]],["keyword/17-34",[]],["title/17-35",[9,22.686,573,39.909]],["name/17-35",[]],["text/17-35",[]],["component/17-35",[]],["keyword/17-35",[]],["title/17-36",[167,27.302,573,34.26,667,28.168]],["name/17-36",[]],["text/17-36",[]],["component/17-36",[]],["keyword/17-36",[]],["title/17-37",[170,30.441,574,39.909]],["name/17-37",[]],["text/17-37",[]],["component/17-37",[]],["keyword/17-37",[]],["title/17-38",[9,22.686,574,39.909]],["name/17-38",[]],["text/17-38",[]],["component/17-38",[]],["keyword/17-38",[]],["title/17-39",[9,19.475,574,34.26,2148,49.119]],["name/17-39",[]],["text/17-39",[]],["component/17-39",[]],["keyword/17-39",[]],["title/17-40",[120,22.314,121,22.314,323,26.068,574,26.701,1609,29.944]],["name/17-40",[]],["text/17-40",[]],["component/17-40",[]],["keyword/17-40",[]],["title/17-41",[9,13.67,11,9.873,116,22.008,232,22.464,480,14.955,573,24.048]],["name/17-41",[]],["text/17-41",[]],["component/17-41",[]],["keyword/17-41",[]],["title/17-42",[167,27.302,574,34.26,667,28.168]],["name/17-42",[]],["text/17-42",[]],["component/17-42",[]],["keyword/17-42",[]],["title/17-43",[120,25.081,121,25.081,323,29.301,1609,33.658]],["name/17-43",[]],["text/17-43",[]],["component/17-43",[]],["keyword/17-43",[]],["title/17-44",[120,25.081,121,25.081,686,31.641,753,23.22]],["name/17-44",[]],["text/17-44",[]],["component/17-44",[]],["keyword/17-44",[]],["title/17-45",[686,36.12,753,26.506,2149,54.092]],["name/17-45",[]],["text/17-45",[]],["component/17-45",[]],["keyword/17-45",[]],["title/17-46",[9,17.06,571,30.012,686,31.641,753,23.22]],["name/17-46",[]],["text/17-46",[]],["component/17-46",[]],["keyword/17-46",[]],["title/17-47",[348,32.297,571,39.909]],["name/17-47",[]],["text/17-47",[]],["component/17-47",[]],["keyword/17-47",[]],["title/17-48",[571,34.26,653,41.442,2150,49.119]],["name/17-48",[]],["text/17-48",[]],["component/17-48",[]],["keyword/17-48",[]],["title/17-49",[571,30.012,610,36.303,653,36.303,2151,43.028]],["name/17-49",[]],["text/17-49",[]],["component/17-49",[]],["keyword/17-49",[]],["title/17-50",[232,32.003,480,21.306,571,34.26]],["name/17-50",[]],["text/17-50",[]],["component/17-50",[]],["keyword/17-50",[]],["title/17-51",[167,23.916,667,24.675,686,31.641,753,23.22]],["name/17-51",[]],["text/17-51",[]],["component/17-51",[]],["keyword/17-51",[]],["title/17-52",[1296,50.384]],["name/17-52",[]],["text/17-52",[]],["component/17-52",[]],["keyword/17-52",[]],["title/17",[11,14.066,120,28.631,121,28.631]],["name/17",[2152,0.861]],["text/17",[1,1.202,3,0.212,6,0.172,7,0.169,8,0.342,9,2.343,10,0.474,11,1.596,13,0.191,17,0.844,19,1.487,21,2.875,22,0.466,25,0.212,36,1.586,44,0.318,46,1.128,51,1.288,53,0.2,55,0.186,62,0.2,66,0.572,67,0.356,68,0.335,69,0.342,73,0.625,76,1.4,82,0.482,83,1.991,89,1.679,91,0.878,95,0.287,99,3.397,101,0.156,109,0.329,111,1.876,112,0.607,113,1.728,114,0.443,115,1.906,116,1.338,120,3.516,121,3.444,122,1.11,123,0.521,124,1.016,125,1.633,129,2.128,137,0.539,139,0.869,142,0.572,143,0.427,144,0.51,147,0.628,148,1.086,149,0.402,151,1.542,153,0.796,154,0.191,156,1.428,158,0.443,159,1.086,160,0.628,161,0.766,162,0.329,166,0.443,167,0.156,168,0.544,170,1.308,172,0.696,173,1.187,174,0.628,175,2.273,176,0.91,177,3.177,178,0.247,181,0.191,182,0.544,183,0.427,184,0.625,185,0.227,186,0.935,190,0.729,191,3.38,192,0.2,194,2.592,195,0.342,198,0.597,199,1.169,201,1.586,205,0.236,209,0.175,213,2.661,219,0.372,221,0.963,223,0.206,227,3.149,228,0.391,229,0.675,231,0.391,232,1.602,233,0.247,234,0.606,237,2.714,242,0.993,243,1.159,244,0.606,245,1.016,246,2.066,253,0.227,255,0.2,256,2.552,259,1.396,260,0.329,263,0.247,264,1.675,268,0.572,270,0.625,271,0.2,272,0.236,273,0.236,274,0.709,275,0.726,276,0.652,278,2.122,281,0.606,283,1.923,288,0.179,295,0.169,298,0.236,300,0.186,306,0.161,307,0.182,308,0.878,309,0.625,310,0.364,317,0.726,321,3.096,323,2.213,333,1.167,337,0.212,340,0.172,341,0.532,345,0.444,346,0.2,348,0.308,349,0.391,351,1.685,357,1.504,358,0.191,364,0.993,372,0.51,383,2.112,385,0.521,393,0.324,394,0.356,399,0.206,404,1.243,406,0.247,408,0.236,409,0.745,411,0.182,413,0.206,415,0.414,418,0.147,424,0.402,431,1.073,433,0.414,436,0.191,437,0.206,441,0.186,461,0.349,470,1.187,471,0.227,472,0.247,473,0.427,474,0.381,476,0.828,478,0.866,479,4.215,480,1.796,481,0.191,485,0.28,489,0.28,490,0.372,495,0.2,496,0.675,500,0.182,501,0.261,504,0.796,510,0.91,512,0.182,515,0.195,516,1.328,520,0.572,522,0.482,523,0.745,525,1.04,526,0.91,530,1.834,534,0.881,535,1.76,545,0.247,549,1.489,550,2.016,551,3.423,553,1.159,555,0.227,557,1.4,561,0.195,562,0.28,563,0.427,564,0.443,569,3.336,570,3.194,571,3.252,572,4.58,573,4.032,574,3.733,576,0.814,586,0.746,594,0.227,595,0.402,596,0.195,599,0.963,601,0.329,604,0.227,608,0.402,609,0.381,610,3.851,612,2.428,613,3.82,614,1.209,619,2.813,622,1.252,624,0.675,635,0.482,638,0.546,646,0.191,648,0.381,650,1.992,653,1.258,654,1.64,656,2.896,657,1.318,664,0.236,665,0.532,666,0.414,667,0.161,676,0.219,678,0.402,680,1.258,682,0.844,686,2.69,694,0.206,697,0.588,703,0.935,706,0.51,711,0.28,714,0.236,717,0.236,719,0.212,721,2.066,725,0.247,728,1.715,730,1.38,731,1.442,732,0.935,733,1.428,734,0.236,735,0.227,736,0.219,741,0.2,752,2.788,753,2.969,754,3.647,756,0.236,758,0.482,763,0.381,765,0.972,768,0.91,772,1.073,773,0.28,775,3.618,776,0.935,777,1.041,779,0.402,780,0.261,781,1.504,785,0.972,792,0.206,799,0.427,800,0.482,802,0.195,819,0.227,837,0.414,849,0.443,851,0.814,853,0.219,862,0.443,886,0.866,900,0.186,901,5.805,928,1.123,931,3.848,932,0.532,941,0.247,944,0.443,949,0.206,955,0.935,956,0.247,959,0.402,963,1.296,974,0.372,979,0.665,983,0.402,1024,0.963,1034,0.191,1042,0.532,1056,0.195,1071,0.227,1074,0.482,1080,0.588,1085,0.706,1086,0.814,1095,0.675,1096,0.963,1106,0.2,1113,0.227,1114,0.443,1115,0.261,1116,0.261,1121,0.558,1124,0.212,1137,0.236,1138,0.427,1139,0.227,1140,1.919,1152,2.895,1160,3.403,1161,2.593,1162,0.745,1163,0.648,1175,0.588,1178,0.414,1182,0.212,1183,0.814,1187,0.443,1189,0.745,1190,0.414,1221,0.219,1277,0.814,1284,1.073,1293,0.844,1294,1.682,1297,0.788,1299,1.159,1304,0.261,1316,0.878,1324,0.206,1340,1.073,1351,0.28,1368,0.236,1371,0.443,1373,0.261,1399,0.28,1401,0.648,1403,0.236,1421,0.247,1429,0.28,1431,0.261,1434,0.51,1473,0.236,1475,0.482,1477,1.392,1489,0.706,1500,0.247,1532,1.554,1548,0.878,1568,0.8,1573,0.51,1576,0.247,1609,1.785,1610,0.236,1614,0.482,1629,0.28,1636,1.031,1682,0.247,1689,0.247,1693,1.123,1699,0.92,1701,0.227,1713,0.461,1719,0.746,1720,0.878,1723,0.236,1730,0.546,1731,0.972,1734,1.073,1742,0.8,1745,0.28,1746,0.28,1755,0.461,1762,1.041,1767,0.261,1772,0.236,1781,0.247,1786,0.482,1788,0.51,1800,1.606,1805,1.777,1812,1.436,1819,0.8,1823,1.041,1824,0.51,1827,0.28,1828,0.8,1830,0.546,1832,1.777,1833,0.51,1834,0.706,1836,0.247,1863,1.271,1864,0.261,1880,1.041,1881,1.392,1882,0.675,1890,0.706,1898,1.504,1899,0.227,1909,0.8,1917,0.236,1922,0.8,1926,0.236,1980,0.92,1982,0.546,1989,1.589,1995,0.92,1998,0.546,2029,1.38,2030,0.546,2044,1.777,2051,1.318,2070,0.51,2072,0.706,2086,0.546,2088,0.28,2092,1.041,2125,1.392,2147,1.123,2148,0.28,2150,0.546,2151,3.391,2153,0.308,2154,0.308,2155,0.28,2156,0.308,2157,0.601,2158,0.88,2159,0.28,2160,0.746,2161,0.308,2162,1.702,2163,0.601,2164,0.308,2165,0.546,2166,0.546,2167,0.88,2168,0.308,2169,4.85,2170,1.874,2171,0.28,2172,1.271,2173,0.308,2174,1.041,2175,1.643,2176,0.601,2177,0.308,2178,0.308,2179,0.28,2180,0.308,2181,0.308,2182,1.4,2183,0.601,2184,2.309,2185,1.4,2186,0.308,2187,0.308,2188,0.8,2189,1.492,2190,1.146,2191,1.146,2192,1.492,2193,0.308,2194,1.643,2195,0.601,2196,1.643,2197,0.88,2198,0.88,2199,0.601,2200,1.146,2201,0.601,2202,2.951,2203,1.643,2204,0.88,2205,3.579,2206,1.874,2207,1.874,2208,0.88,2209,0.601,2210,0.308,2211,0.308,2212,0.308,2213,0.88,2214,0.601,2215,0.308,2216,0.601,2217,0.88,2218,0.308,2219,0.546,2220,0.601,2221,0.308,2222,0.308,2223,0.28,2224,0.28,2225,0.308,2226,0.88,2227,0.308,2228,0.308,2229,0.308,2230,0.546,2231,1.041,2232,0.308,2233,0.601,2234,0.601,2235,0.261,2236,0.308,2237,0.28,2238,0.261,2239,0.308,2240,0.308,2241,0.308,2242,0.308,2243,0.88,2244,0.308,2245,0.88,2246,0.601,2247,0.88,2248,0.546,2249,0.601,2250,0.601,2251,0.88,2252,1.146,2253,0.28,2254,0.308,2255,0.308,2256,0.308,2257,0.308,2258,0.308,2259,0.308,2260,0.88,2261,1.146,2262,1.146,2263,1.146,2264,0.308,2265,0.601,2266,1.146,2267,2.096,2268,0.88,2269,0.308,2270,1.146,2271,1.146,2272,1.146,2273,0.308,2274,0.308,2275,0.601,2276,0.261,2277,1.146,2278,0.28,2279,0.308,2280,0.261,2281,0.261,2282,0.308,2283,0.308,2284,0.601,2285,0.601,2286,0.308,2287,0.88,2288,0.601,2289,0.308,2290,0.308,2291,0.308,2292,0.308,2293,0.308,2294,0.308,2295,0.308,2296,0.308,2297,0.308,2298,0.601,2299,0.308,2300,0.601,2301,0.308,2302,0.308,2303,0.308,2304,0.601,2305,0.28,2306,0.308,2307,0.601,2308,0.88,2309,0.601,2310,0.601,2311,0.308,2312,0.308,2313,0.308,2314,0.308,2315,0.308,2316,0.308,2317,0.308,2318,0.308,2319,0.308,2320,0.308,2321,0.308,2322,0.308,2323,1.146,2324,0.308,2325,0.601,2326,0.88,2327,0.308,2328,0.308,2329,0.308,2330,0.308,2331,0.308,2332,0.28,2333,0.308,2334,0.308,2335,0.601,2336,0.308,2337,0.601,2338,0.308,2339,0.308,2340,0.28,2341,0.308,2342,0.546,2343,0.308,2344,0.308,2345,0.601,2346,0.308,2347,0.308,2348,0.8,2349,0.308,2350,0.308,2351,0.308,2352,0.308,2353,0.601,2354,0.601,2355,0.601,2356,0.88,2357,0.308,2358,0.308,2359,0.308,2360,0.601,2361,0.308,2362,0.308,2363,0.308,2364,0.308,2365,0.308,2366,0.308,2367,0.308,2368,0.308,2369,0.308,2370,0.308]],["component/17",[11,0.17,12,0.288]],["keyword/17",[]],["title/18-1",[43,35.147,290,36.524]],["name/18-1",[]],["text/18-1",[]],["component/18-1",[]],["keyword/18-1",[]],["title/18-2",[11,14.066,392,29.118,589,36.12]],["name/18-2",[]],["text/18-2",[]],["component/18-2",[]],["keyword/18-2",[]],["title/18-3",[322,32.297,967,43.338]],["name/18-3",[]],["text/18-3",[]],["component/18-3",[]],["keyword/18-3",[]],["title/18-4",[2371,55.538]],["name/18-4",[]],["text/18-4",[]],["component/18-4",[]],["keyword/18-4",[]],["title/18-5",[322,38.674]],["name/18-5",[]],["text/18-5",[]],["component/18-5",[]],["keyword/18-5",[]],["title/18-6",[411,37.28,608,42.076]],["name/18-6",[]],["text/18-6",[]],["component/18-6",[]],["keyword/18-6",[]],["title/18-7",[2372,60.532]],["name/18-7",[]],["text/18-7",[]],["component/18-7",[]],["keyword/18-7",[]],["title/18-8",[589,42.076,2061,44.757]],["name/18-8",[]],["text/18-8",[]],["component/18-8",[]],["keyword/18-8",[]],["title/18-9",[11,9.873,195,21.581,270,26.969,534,17.845,589,25.354,830,21.581]],["name/18-9",[]],["text/18-9",[]],["component/18-9",[]],["keyword/18-9",[]],["title/18-10",[418,22.576,657,38.015,830,26.933,2373,43.028]],["name/18-10",[]],["text/18-10",[]],["component/18-10",[]],["keyword/18-10",[]],["title/18-11",[418,22.576,830,26.933,1489,38.015,2371,34.878]],["name/18-11",[]],["text/18-11",[]],["component/18-11",[]],["keyword/18-11",[]],["title/18-12",[195,23.961,830,23.961,923,33.821,1755,32.298,2371,31.03]],["name/18-12",[]],["text/18-12",[]],["component/18-12",[]],["keyword/18-12",[]],["title/18-13",[337,32.59,830,26.933,2044,40.158,2371,34.878]],["name/18-13",[]],["text/18-13",[]],["component/18-13",[]],["keyword/18-13",[]],["title/18-14",[24,29.118,322,27.725,2069,45.843]],["name/18-14",[]],["text/18-14",[]],["component/18-14",[]],["keyword/18-14",[]],["title/18-15",[1127,50.551,2374,57.218]],["name/18-15",[]],["text/18-15",[]],["component/18-15",[]],["keyword/18-15",[]],["title/18-16",[2375,63.946]],["name/18-16",[]],["text/18-16",[]],["component/18-16",[]],["keyword/18-16",[]],["title/18-17",[2376,68.515]],["name/18-17",[]],["text/18-17",[]],["component/18-17",[]],["keyword/18-17",[]],["title/18-18",[2377,53.402,2378,57.218]],["name/18-18",[]],["text/18-18",[]],["component/18-18",[]],["keyword/18-18",[]],["title/18-19",[322,32.297,592,53.402]],["name/18-19",[]],["text/18-19",[]],["component/18-19",[]],["keyword/18-19",[]],["title/18-20",[322,32.297,835,39.909]],["name/18-20",[]],["text/18-20",[]],["component/18-20",[]],["keyword/18-20",[]],["title/18-21",[1,32.812,2375,53.402]],["name/18-21",[]],["text/18-21",[]],["component/18-21",[]],["keyword/18-21",[]],["title/18-22",[348,32.297,589,42.076]],["name/18-22",[]],["text/18-22",[]],["component/18-22",[]],["keyword/18-22",[]],["title/18-23",[1121,39.909,2061,44.757]],["name/18-23",[]],["text/18-23",[]],["component/18-23",[]],["keyword/18-23",[]],["title/18-24",[1397,46.38,2061,44.757]],["name/18-24",[]],["text/18-24",[]],["component/18-24",[]],["keyword/18-24",[]],["title/18-25",[9,19.475,752,27.725,2379,49.119]],["name/18-25",[]],["text/18-25",[]],["component/18-25",[]],["keyword/18-25",[]],["title/18-26",[9,17.06,260,25.957,2371,34.878,2380,43.028]],["name/18-26",[]],["text/18-26",[]],["component/18-26",[]],["keyword/18-26",[]],["title/18-27",[322,27.725,1397,39.815,1800,41.442]],["name/18-27",[]],["text/18-27",[]],["component/18-27",[]],["keyword/18-27",[]],["title/18-28",[2061,44.757,2381,57.218]],["name/18-28",[]],["text/18-28",[]],["component/18-28",[]],["keyword/18-28",[]],["title/18-29",[322,32.297,2074,50.551]],["name/18-29",[]],["text/18-29",[]],["component/18-29",[]],["keyword/18-29",[]],["title/18-30",[322,32.297,811,40.941]],["name/18-30",[]],["text/18-30",[]],["component/18-30",[]],["keyword/18-30",[]],["title/18-31",[974,38.963,2072,50.551]],["name/18-31",[]],["text/18-31",[]],["component/18-31",[]],["keyword/18-31",[]],["title/18-32",[479,37.28,1128,50.551]],["name/18-32",[]],["text/18-32",[]],["component/18-32",[]],["keyword/18-32",[]],["title/18-33",[177,26.132,295,29.631,534,25.423]],["name/18-33",[]],["text/18-33",[]],["component/18-33",[]],["keyword/18-33",[]],["title/18-34",[122,25.081,322,24.287,593,32.59,2372,38.015]],["name/18-34",[]],["text/18-34",[]],["component/18-34",[]],["keyword/18-34",[]],["title/18-35",[167,23.916,322,24.287,667,24.675,2074,38.015]],["name/18-35",[]],["text/18-35",[]],["component/18-35",[]],["keyword/18-35",[]],["title/18-36",[217,29.118,322,27.725,1142,35.146]],["name/18-36",[]],["text/18-36",[]],["component/18-36",[]],["keyword/18-36",[]],["title/18-37",[190,24.675,516,30.012,1701,34.878,2085,40.158]],["name/18-37",[]],["text/18-37",[]],["component/18-37",[]],["keyword/18-37",[]],["title/18-38",[190,28.168,322,27.725,905,39.815]],["name/18-38",[]],["text/18-38",[]],["component/18-38",[]],["keyword/18-38",[]],["title/18-39",[99,41.442,322,27.725,2382,49.119]],["name/18-39",[]],["text/18-39",[]],["component/18-39",[]],["keyword/18-39",[]],["title/18-40",[198,28.168,648,34.26,1888,43.396]],["name/18-40",[]],["text/18-40",[]],["component/18-40",[]],["keyword/18-40",[]],["title/18-41",[155,32.59,323,29.301,703,31.641,2383,40.158]],["name/18-41",[]],["text/18-41",[]],["component/18-41",[]],["keyword/18-41",[]],["title/18-42",[77,50.551,2384,57.218]],["name/18-42",[]],["text/18-42",[]],["component/18-42",[]],["keyword/18-42",[]],["title/18-43",[322,27.725,686,36.12,771,45.843]],["name/18-43",[]],["text/18-43",[]],["component/18-43",[]],["keyword/18-43",[]],["title/18-44",[243,29.301,250,38.015,322,24.287,2385,40.158]],["name/18-44",[]],["text/18-44",[]],["component/18-44",[]],["keyword/18-44",[]],["title/18-45",[400,55.538]],["name/18-45",[]],["text/18-45",[]],["component/18-45",[]],["keyword/18-45",[]],["title/18",[11,16.386,589,42.076]],["name/18",[52,0.729]],["text/18",[1,0.768,6,0.674,7,1.326,8,0.185,9,1.987,11,1.408,13,0.393,17,0.684,18,0.224,19,1.676,21,2.175,22,1.393,24,1.73,27,0.425,28,1.022,36,0.509,40,1.449,43,0.182,44,0.173,46,1.303,48,0.207,49,0.197,50,0.24,51,1.552,52,0.277,55,0.384,62,0.785,66,3.585,67,0.193,68,0.674,69,1.826,70,0.296,76,1.154,77,0.262,82,1.441,83,3.354,89,1.966,95,1.254,96,0.24,98,0.25,99,1.13,101,0.744,106,1.591,110,0.575,111,2.284,112,1.602,113,1.188,114,0.468,115,2.336,117,0.744,118,0.173,122,1.281,123,1.165,124,0.912,125,0.413,126,0.912,127,0.934,129,2.849,130,0.212,137,2.814,139,1.5,143,0.232,147,1.205,148,1.403,150,0.731,151,1.616,154,0.393,155,2.21,156,0.528,159,1.001,160,2.042,161,1.892,162,0.808,164,0.24,166,0.24,167,0.994,170,1.462,172,1.598,173,1.247,174,0.179,175,0.326,177,3.014,178,0.262,181,0.747,186,0.218,189,0.296,190,3.65,191,3.187,194,2.119,195,0.687,197,0.197,198,1.475,199,0.674,201,0.808,202,1.937,205,0.25,209,0.361,212,1.449,213,0.838,217,1.918,218,0.185,219,0.393,220,0.712,221,0.437,222,0.296,223,0.218,224,2.228,226,0.262,227,2.648,228,0.785,229,0.25,230,0.277,231,1.71,233,0.746,234,0.639,235,0.487,236,0.24,237,1.275,238,0.25,239,0.604,242,0.562,243,1.987,244,0.639,245,0.575,246,1.463,250,0.746,251,0.788,253,0.24,255,1.429,256,0.207,259,0.891,260,1.552,264,1.36,265,0.202,271,0.413,272,0.487,273,1.509,274,0.747,275,1.094,276,2.461,278,0.838,281,0.224,283,0.55,285,0.224,288,1.141,290,0.189,292,0.342,294,0.218,295,1.205,296,0.342,298,0.712,300,0.562,302,0.51,303,0.303,307,0.376,309,0.452,310,1.044,315,0.158,317,1.094,321,2.342,322,5.233,323,0.575,325,3.985,326,0.262,332,0.25,333,0.232,337,1.81,340,0.182,341,0.562,342,0.982,345,0.872,346,0.212,348,0.476,349,1.429,351,0.808,355,0.684,358,0.912,363,0.413,364,1.942,383,0.413,385,0.55,392,0.176,393,1.06,394,0.715,395,0.277,399,0.425,404,1.06,406,0.262,408,0.25,409,1.279,411,1.901,412,0.218,413,0.425,415,0.224,416,0.262,417,0.393,418,1.531,427,0.89,428,0.844,431,0.487,432,0.715,434,0.51,436,0.912,441,1.19,460,0.262,461,0.539,463,0.858,464,0.25,468,0.468,469,0.712,470,1.247,472,1.183,473,0.232,474,0.403,475,0.807,476,1.022,477,0.425,478,3.146,479,1.557,480,0.581,481,0.575,490,0.393,492,0.24,495,0.212,499,0.218,500,0.715,504,2.461,505,0.437,510,0.785,512,0.872,515,0.589,516,0.765,518,0.25,519,0.296,520,0.212,522,0.97,523,0.413,525,0.207,526,0.785,527,0.262,530,0.934,534,1.752,535,0.958,537,0.368,544,0.262,549,0.452,550,0.262,551,3.807,552,0.25,553,0.912,557,0.807,559,0.262,561,0.403,572,0.403,576,1.227,580,0.55,581,0.621,582,0.202,584,0.262,585,0.224,589,4.166,590,0.51,592,2.565,593,2.21,595,0.218,596,0.765,601,0.179,608,1.892,609,0.934,610,0.487,612,0.66,614,0.24,618,0.452,619,2.787,622,1.469,624,0.487,626,0.437,627,0.788,635,0.262,636,0.277,637,0.224,640,0.51,643,1.085,646,0.747,648,1.667,650,0.831,654,1.399,655,1.024,659,0.51,661,0.89,662,0.621,663,0.296,664,0.487,665,1.83,666,0.224,667,0.768,669,0.425,670,0.452,674,0.296,675,0.277,678,0.425,682,0.684,685,0.232,686,0.218,690,0.577,692,0.277,694,0.218,697,0.621,699,0.296,700,0.296,702,0.296,703,0.425,706,0.539,715,0.277,717,0.25,719,1.354,720,0.788,722,0.621,724,0.277,725,0.262,728,0.589,731,1.188,733,0.575,734,0.25,735,0.684,738,1.765,740,0.277,741,0.785,752,0.326,753,0.592,754,1.275,755,1.279,758,0.51,763,0.207,764,0.621,766,0.277,769,0.844,771,0.539,772,0.487,774,0.25,775,0.684,776,0.218,779,1.315,788,0.468,789,0.25,790,0.262,792,0.621,795,0.277,798,1.765,800,0.97,802,0.589,811,1.573,820,0.25,830,0.838,835,0.207,849,0.468,854,0.452,860,0.452,865,0.25,869,0.24,878,0.262,886,0.202,900,0.197,901,4.958,905,1.085,926,0.262,931,2.254,932,1.942,935,0.277,939,2.111,941,0.51,944,0.468,947,0.232,948,0.539,949,0.807,954,0.202,955,0.425,956,0.262,957,0.232,959,0.218,967,0.831,971,0.731,974,2.498,979,1.403,982,0.487,983,0.218,990,1.014,1003,0.207,1007,0.212,1023,1.81,1034,0.393,1042,0.197,1053,0.25,1056,0.207,1080,0.807,1086,0.66,1094,0.262,1095,1.13,1096,0.437,1099,0.212,1103,0.24,1106,0.212,1113,0.24,1114,1.619,1121,1.533,1127,2.991,1128,0.51,1137,0.25,1138,0.232,1140,0.702,1142,0.958,1152,0.575,1160,1.463,1161,0.425,1162,0.413,1163,0.684,1183,0.66,1184,0.25,1185,0.24,1189,0.604,1209,0.24,1214,0.621,1215,0.684,1217,0.539,1225,0.25,1284,0.926,1293,0.24,1297,0.437,1298,0.262,1299,1.497,1301,0.277,1309,0.25,1321,0.487,1322,0.468,1324,0.218,1338,0.277,1340,0.487,1348,0.51,1361,0.277,1367,0.577,1371,0.24,1374,0.926,1380,0.788,1384,0.296,1388,0.296,1397,1.619,1401,0.684,1403,1.324,1408,0.277,1425,0.296,1467,0.539,1470,0.487,1473,1.13,1475,0.746,1490,0.262,1511,0.277,1525,1.339,1532,0.202,1534,0.262,1538,0.487,1540,0.277,1548,0.926,1549,0.277,1576,1.58,1579,0.539,1583,0.277,1585,0.51,1591,0.539,1592,0.51,1594,0.277,1609,0.232,1611,0.788,1614,0.262,1630,0.262,1631,0.844,1634,0.262,1635,0.277,1636,0.468,1645,0.296,1652,0.24,1668,0.296,1676,0.296,1685,0.296,1687,0.277,1688,0.296,1689,0.262,1690,0.296,1696,0.296,1699,0.262,1700,0.277,1701,1.937,1703,1.097,1708,0.262,1709,0.277,1713,0.487,1720,0.25,1723,0.926,1734,0.712,1755,2.73,1765,0.262,1793,0.262,1797,0.788,1800,0.926,1801,0.296,1812,2.016,1815,0.296,1824,0.277,1832,0.539,1836,0.262,1859,0.296,1864,0.539,1882,0.487,1883,0.296,1884,0.577,1888,0.262,1890,0.97,1891,0.296,1893,0.296,1894,0.277,1898,0.746,1899,0.468,1917,0.25,1925,0.577,1926,0.25,1930,0.296,1938,0.277,1945,0.844,1950,0.539,1960,0.296,1980,0.746,1995,0.262,2029,0.89,2051,0.51,2059,0.296,2061,3.268,2065,1.569,2066,0.296,2067,0.277,2069,2.23,2070,0.277,2072,1.765,2074,2.859,2080,0.296,2085,2.565,2102,0.296,2125,0.277,2155,0.296,2160,0.277,2162,0.296,2165,0.577,2166,0.577,2169,0.296,2171,0.577,2172,0.296,2192,0.296,2219,0.577,2223,1.097,2224,0.296,2230,0.296,2231,0.296,2235,0.277,2237,0.577,2238,0.277,2248,0.296,2253,0.296,2276,0.277,2278,0.296,2280,0.539,2332,0.296,2342,0.296,2371,6.34,2372,2.273,2373,2.389,2374,1.097,2375,2.723,2376,1.569,2377,0.539,2378,1.339,2379,1.097,2380,0.577,2381,2.389,2382,0.844,2383,0.277,2384,0.577,2385,0.539,2386,0.326,2387,0.326,2388,0.577,2389,0.636,2390,1.474,2391,0.326,2392,0.326,2393,0.326,2394,0.326,2395,0.326,2396,0.929,2397,0.929,2398,0.326,2399,0.326,2400,0.326,2401,0.277,2402,0.636,2403,0.326,2404,0.929,2405,0.326,2406,0.929,2407,0.326,2408,0.326,2409,0.636,2410,0.326,2411,0.326,2412,0.326,2413,0.326,2414,0.326,2415,0.326,2416,0.929,2417,0.929,2418,0.929,2419,1.969,2420,0.929,2421,0.636,2422,0.636,2423,1.728,2424,0.929,2425,0.326,2426,0.929,2427,1.474,2428,0.326,2429,0.326,2430,0.636,2431,0.326,2432,0.326,2433,0.326,2434,1.788,2435,1.474,2436,0.326,2437,0.326,2438,0.929,2439,0.326,2440,0.326,2441,0.326,2442,0.326,2443,0.326,2444,0.636,2445,0.326,2446,0.326,2447,0.326,2448,2.573,2449,0.636,2450,0.296,2451,0.326,2452,0.636,2453,0.326,2454,0.636,2455,0.636,2456,0.326,2457,0.326,2458,0.326,2459,1.209,2460,0.326,2461,0.636,2462,0.326,2463,0.636,2464,0.326,2465,0.636,2466,0.326,2467,0.929,2468,0.326,2469,0.326,2470,0.326,2471,0.326,2472,0.296,2473,0.326,2474,0.326,2475,0.326,2476,0.326,2477,0.326,2478,0.326,2479,0.326,2480,0.326,2481,0.326,2482,0.326,2483,0.326,2484,0.326,2485,0.326,2486,0.326,2487,0.326,2488,0.296,2489,0.326,2490,0.326,2491,0.326,2492,0.636,2493,0.51,2494,0.51,2495,0.577,2496,0.326,2497,0.326,2498,0.929,2499,0.326,2500,0.326,2501,0.326,2502,0.326,2503,0.636,2504,0.636,2505,0.636,2506,0.326,2507,0.636,2508,0.326,2509,0.326,2510,0.326,2511,0.929,2512,0.326,2513,0.326,2514,0.636,2515,0.326,2516,0.326,2517,0.326,2518,0.326,2519,0.326,2520,0.326,2521,0.326,2522,0.326,2523,0.636,2524,0.326,2525,0.326,2526,0.326,2527,0.577,2528,0.636,2529,0.326,2530,0.326,2531,0.326,2532,0.929,2533,0.326,2534,0.326,2535,0.326,2536,0.636,2537,0.326,2538,0.326,2539,0.277,2540,0.326,2541,0.326,2542,0.636,2543,0.326,2544,0.326,2545,0.636,2546,0.326,2547,0.326,2548,0.296,2549,0.326,2550,0.326,2551,0.326,2552,0.326,2553,0.929,2554,0.326,2555,0.326,2556,0.326,2557,0.326,2558,0.326,2559,0.326,2560,0.326,2561,0.326,2562,0.326,2563,0.326,2564,0.326,2565,0.326,2566,0.326,2567,0.326,2568,0.326,2569,0.326,2570,0.326,2571,0.326,2572,0.326,2573,0.326]],["component/18",[11,0.17,12,0.288]],["keyword/18",[]],["title/19-1",[43,35.147,290,36.524]],["name/19-1",[]],["text/19-1",[]],["component/19-1",[]],["keyword/19-1",[]],["title/19-2",[119,28.631,307,32.003,392,29.118]],["name/19-2",[]],["text/19-2",[]],["component/19-2",[]],["keyword/19-2",[]],["title/19-3",[119,25.081,218,26.933,296,25.508,319,25.957]],["name/19-3",[]],["text/19-3",[]],["component/19-3",[]],["keyword/19-3",[]],["title/19-4",[119,33.352,971,38.09]],["name/19-4",[]],["text/19-4",[]],["component/19-4",[]],["keyword/19-4",[]],["title/19-5",[11,12.322,119,25.081,348,24.287,2574,32.59]],["name/19-5",[]],["text/19-5",[]],["component/19-5",[]],["keyword/19-5",[]],["title/19-6",[500,37.28,2574,43.338]],["name/19-6",[]],["text/19-6",[]],["component/19-6",[]],["keyword/19-6",[]],["title/19-7",[260,34.517,2574,43.338]],["name/19-7",[]],["text/19-7",[]],["component/19-7",[]],["keyword/19-7",[]],["title/19-8",[9,15.178,168,26.068,218,23.961,296,22.694,319,23.093]],["name/19-8",[]],["text/19-8",[]],["component/19-8",[]],["keyword/19-8",[]],["title/19-9",[307,44.641]],["name/19-9",[]],["text/19-9",[]],["component/19-9",[]],["keyword/19-9",[]],["title/19-10",[13,26.068,112,22.314,198,21.953,265,26.068,648,26.701]],["name/19-10",[]],["text/19-10",[]],["component/19-10",[]],["keyword/19-10",[]],["title/19-11",[112,28.631,1118,41.442,2574,37.203]],["name/19-11",[]],["text/19-11",[]],["component/19-11",[]],["keyword/19-11",[]],["title/19-12",[69,35.814,737,50.551]],["name/19-12",[]],["text/19-12",[]],["component/19-12",[]],["keyword/19-12",[]],["title/19-13",[646,26.068,901,23.093,2575,38.281,2576,38.281,2577,38.281]],["name/19-13",[]],["text/19-13",[]],["component/19-13",[]],["keyword/19-13",[]],["title/19-14",[9,17.06,111,22.891,119,25.081,399,31.641]],["name/19-14",[]],["text/19-14",[]],["component/19-14",[]],["keyword/19-14",[]],["title/19-15",[124,33.448,901,29.631,2578,49.119]],["name/19-15",[]],["text/19-15",[]],["component/19-15",[]],["keyword/19-15",[]],["title/19-16",[514,55.538]],["name/19-16",[]],["text/19-16",[]],["component/19-16",[]],["keyword/19-16",[]],["title/19-17",[13,33.448,2493,43.396,2494,43.396]],["name/19-17",[]],["text/19-17",[]],["component/19-17",[]],["keyword/19-17",[]],["title/19-18",[217,33.92,2579,57.218]],["name/19-18",[]],["text/19-18",[]],["component/19-18",[]],["keyword/19-18",[]],["title/19-19",[11,12.322,119,25.081,199,26.431,404,25.508]],["name/19-19",[]],["text/19-19",[]],["component/19-19",[]],["keyword/19-19",[]],["title/19-20",[11,12.322,119,25.081,480,18.664,1077,31.641]],["name/19-20",[]],["text/19-20",[]],["component/19-20",[]],["keyword/19-20",[]],["title/19-21",[172,26.896,802,34.26,1160,32.699]],["name/19-21",[]],["text/19-21",[]],["component/19-21",[]],["keyword/19-21",[]],["title/19-22",[11,12.322,119,25.081,619,25.957,798,38.015]],["name/19-22",[]],["text/19-22",[]],["component/19-22",[]],["keyword/19-22",[]],["title/19-23",[111,26.132,119,28.631,608,36.12]],["name/19-23",[]],["text/19-23",[]],["component/19-23",[]],["keyword/19-23",[]],["title/19-24",[118,33.352,177,30.441]],["name/19-24",[]],["text/19-24",[]],["component/19-24",[]],["keyword/19-24",[]],["title/19-25",[177,40.171]],["name/19-25",[]],["text/19-25",[]],["component/19-25",[]],["keyword/19-25",[]],["title/19-26",[218,35.814,2580,57.218]],["name/19-26",[]],["text/19-26",[]],["component/19-26",[]],["keyword/19-26",[]],["title/19-27",[218,35.814,1077,42.076]],["name/19-27",[]],["text/19-27",[]],["component/19-27",[]],["keyword/19-27",[]],["title/19-28",[119,22.314,198,21.953,625,26.068,2574,28.995,2581,38.281]],["name/19-28",[]],["text/19-28",[]],["component/19-28",[]],["keyword/19-28",[]],["title/19-29",[112,22.314,198,21.953,265,26.068,648,26.701,789,32.298]],["name/19-29",[]],["text/19-29",[]],["component/19-29",[]],["keyword/19-29",[]],["title/19-30",[265,26.068,438,33.821,747,26.068,886,26.068,2574,28.995]],["name/19-30",[]],["text/19-30",[]],["component/19-30",[]],["keyword/19-30",[]],["title/19-31",[217,33.92,580,37.28]],["name/19-31",[]],["text/19-31",[]],["component/19-31",[]],["keyword/19-31",[]],["title/19-32",[260,29.631,265,33.448,747,33.448]],["name/19-32",[]],["text/19-32",[]],["component/19-32",[]],["keyword/19-32",[]],["title/19-33",[67,37.28,789,48.275]],["name/19-33",[]],["text/19-33",[]],["component/19-33",[]],["keyword/19-33",[]],["title/19-34",[217,22.694,218,23.961,246,25.484,404,22.694,2582,35.728]],["name/19-34",[]],["text/19-34",[]],["component/19-34",[]],["keyword/19-34",[]],["title/19-35",[218,30.745,480,21.306,2582,45.843]],["name/19-35",[]],["text/19-35",[]],["component/19-35",[]],["keyword/19-35",[]],["title/19-36",[201,34.517,1299,38.963]],["name/19-36",[]],["text/19-36",[]],["component/19-36",[]],["keyword/19-36",[]],["title/19-37",[119,18.281,218,19.63,979,20.019,1900,45.642,2583,34.537,2584,29.27]],["name/19-37",[]],["text/19-37",[]],["component/19-37",[]],["keyword/19-37",[]],["title/19-38",[218,21.581,479,22.464,1077,25.354,2574,26.114,2585,34.478,2586,34.478]],["name/19-38",[]],["text/19-38",[]],["component/19-38",[]],["keyword/19-38",[]],["title/19-39",[218,35.814,1899,46.38]],["name/19-39",[]],["text/19-39",[]],["component/19-39",[]],["keyword/19-39",[]],["title/19-40",[98,29.089,265,23.478,979,22.008,1899,27.947,2587,37.969,2588,34.478]],["name/19-40",[]],["text/19-40",[]],["component/19-40",[]],["keyword/19-40",[]],["title/19-41",[119,25.081,167,23.916,307,28.035,667,24.675]],["name/19-41",[]],["text/19-41",[]],["component/19-41",[]],["keyword/19-41",[]],["title/19-42",[111,22.891,167,23.916,217,25.508,667,24.675]],["name/19-42",[]],["text/19-42",[]],["component/19-42",[]],["keyword/19-42",[]],["title/19-43",[400,55.538]],["name/19-43",[]],["text/19-43",[]],["component/19-43",[]],["keyword/19-43",[]],["title/19",[119,33.352,307,37.28]],["name/19",[2589,0.861]],["text/19",[3,0.365,6,1.978,7,0.291,8,0.578,9,2.671,10,0.286,11,1.929,13,3.062,19,4.578,21,0.548,22,2.02,24,0.286,28,1.112,29,1.19,36,1.029,44,0.281,49,0.321,55,0.321,62,0.345,63,0.365,66,1.221,67,0.602,68,0.568,69,1.676,75,0.482,78,0.482,81,0.862,82,1.239,83,1.511,89,1.311,95,1.818,98,2.005,101,0.74,106,1.582,110,0.328,111,3.139,112,2.401,115,1.978,118,1.385,119,5.356,120,0.281,121,0.281,123,0.314,124,0.906,129,1.942,137,0.689,139,0.994,147,1.239,148,0.308,149,0.979,150,0.886,154,0.328,156,0.833,159,1.089,160,2.362,161,0.979,162,0.557,167,0.948,168,1.162,170,1.093,172,2.362,174,0.803,175,0.272,176,0.345,177,3.774,179,1.507,182,0.328,184,1.041,186,0.355,190,1.178,191,2.129,192,0.952,194,1.851,195,0.578,197,0.321,198,3.384,199,0.568,201,0.557,202,0.391,209,0.302,211,0.615,212,2.809,213,1.285,216,0.482,217,3.794,218,4.73,220,0.407,221,0.7,227,0.377,228,0.661,229,0.407,235,0.78,242,0.321,243,0.906,244,0.365,245,0.328,246,1.582,253,0.391,256,0.645,260,1.434,264,0.629,265,5.181,268,0.661,275,0.336,276,1.851,278,2.923,295,0.291,296,1.011,300,0.615,303,1.818,304,1.548,305,0.355,307,4.048,309,1.335,315,0.908,317,0.645,319,1.239,330,0.816,340,0.296,344,0.391,345,0.268,346,0.345,348,0.751,351,1.239,358,0.629,362,0.391,366,0.355,372,0.308,383,0.952,384,0.391,387,2.957,392,0.286,393,1.217,394,0.314,399,0.355,400,0.391,404,1.409,406,0.816,411,0.602,412,0.355,413,0.355,417,1.618,421,0.45,424,0.355,427,0.391,432,0.314,433,0.365,435,0.391,436,0.328,437,0.355,438,4.786,441,1.782,461,0.308,463,0.377,470,0.336,475,0.68,476,1.338,480,1.604,482,1.242,490,0.906,495,0.345,500,2.93,504,0.578,512,0.314,514,1.383,515,0.645,516,0.928,517,0.482,521,0.355,523,1.221,526,0.345,527,0.426,530,0.336,534,0.883,535,0.661,549,0.377,551,1.618,553,0.906,559,0.426,563,0.723,575,0.482,576,0.377,577,0.482,580,1.112,582,0.328,593,1.008,598,0.979,601,0.803,604,0.391,608,1.748,612,0.377,616,0.391,619,1.784,625,0.629,626,0.7,627,0.45,632,0.355,641,0.661,643,0.391,646,0.629,648,2.73,651,0.377,662,0.355,664,0.78,665,0.321,666,0.365,667,0.763,669,0.355,693,0.391,695,0.407,701,0.482,703,0.68,706,0.308,707,1.331,716,0.45,721,0.886,722,0.68,728,0.336,730,0.391,731,0.365,733,0.328,734,0.407,737,1.176,739,0.816,741,0.345,747,2.518,752,1.817,753,0.921,754,0.59,755,0.345,768,0.345,776,0.68,779,2.368,783,0.426,789,1.733,790,0.426,792,0.355,798,0.426,805,0.281,806,0.314,824,0.391,830,0.578,835,1.867,839,0.816,849,0.391,860,0.723,862,0.391,878,0.426,886,1.398,888,1.242,900,0.321,901,4.919,902,0.45,905,0.749,911,0.45,925,0.377,932,1.969,943,0.45,953,0.816,954,1.162,971,0.321,974,0.906,979,3.281,985,1.507,990,0.7,999,0.336,1012,0.629,1017,0.7,1020,1.292,1023,0.7,1034,0.328,1042,1.582,1053,0.407,1056,0.336,1074,0.426,1077,4.834,1080,0.68,1096,0.365,1099,0.345,1100,0.45,1106,0.345,1114,0.391,1118,3.639,1119,0.45,1120,0.45,1121,0.928,1140,0.253,1142,0.345,1152,1.398,1160,2.461,1161,0.355,1162,1.915,1175,0.979,1272,0.426,1298,0.816,1299,0.629,1324,0.68,1340,0.407,1357,0.482,1368,0.78,1374,0.407,1401,0.749,1421,0.426,1423,2.218,1511,0.862,1513,1.242,1533,0.482,1538,0.78,1545,3.005,1559,0.924,1573,0.862,1585,1.176,1592,1.176,1609,1.041,1626,0.482,1634,0.426,1636,1.926,1663,0.482,1665,2.924,1666,0.45,1700,0.862,1702,0.426,1708,0.426,1713,0.407,1720,0.407,1723,0.407,1744,0.45,1755,0.407,1772,0.407,1781,0.426,1790,0.482,1793,0.426,1797,1.917,1836,0.426,1847,0.816,1857,0.45,1862,3.915,1885,0.482,1888,0.426,1890,0.426,1894,0.862,1895,0.482,1899,0.749,1900,1.242,1917,0.407,1926,0.407,2029,0.391,2037,0.482,2053,1.331,2067,0.45,2135,0.482,2159,0.482,2174,0.482,2179,0.482,2238,0.45,2280,0.45,2281,0.45,2305,1.331,2340,0.482,2348,1.706,2377,0.45,2383,0.45,2385,0.45,2401,1.242,2448,0.482,2472,0.482,2493,0.816,2494,0.816,2495,0.482,2539,0.45,2548,0.482,2574,6.327,2575,0.482,2576,1.706,2577,1.331,2578,2.376,2579,0.482,2580,3.22,2581,2.677,2582,1.242,2584,3.235,2585,3.22,2586,4.12,2588,3.466,2590,1.018,2591,1.879,2592,1.879,2593,0.531,2594,1.018,2595,0.531,2596,0.531,2597,0.531,2598,0.531,2599,1.018,2600,1.018,2601,0.531,2602,0.531,2603,0.531,2604,2.948,2605,1.018,2606,3.257,2607,1.465,2608,1.465,2609,1.465,2610,1.465,2611,2.948,2612,2.617,2613,1.465,2614,0.531,2615,0.531,2616,0.531,2617,0.531,2618,0.531,2619,0.531,2620,0.531,2621,0.531,2622,0.531,2623,0.531,2624,0.531,2625,0.531,2626,0.531,2627,0.531,2628,0.531,2629,0.531,2630,0.531,2631,0.531,2632,1.879,2633,0.531,2634,0.531,2635,0.531,2636,1.018,2637,1.018,2638,0.531,2639,0.531,2640,0.531,2641,1.879,2642,1.465,2643,0.531,2644,3.22,2645,0.531,2646,1.018,2647,1.018,2648,0.531,2649,0.531,2650,0.531,2651,0.531,2652,0.531,2653,0.531,2654,0.482,2655,0.531,2656,2.262,2657,0.531,2658,0.531,2659,0.531,2660,0.531,2661,0.531,2662,0.531,2663,0.531,2664,0.531,2665,1.018,2666,0.531,2667,1.018,2668,0.531,2669,0.531,2670,0.531,2671,0.531,2672,1.018,2673,1.018,2674,0.531,2675,2.262,2676,0.531,2677,0.531,2678,0.531,2679,1.018,2680,1.018,2681,0.531,2682,2.262,2683,1.879,2684,0.531,2685,0.531,2686,0.531,2687,1.465,2688,0.531,2689,1.018,2690,0.531,2691,0.531,2692,0.531,2693,0.531,2694,1.018,2695,0.531,2696,0.531,2697,0.482,2698,0.531,2699,0.924,2700,0.531,2701,0.531,2702,1.018,2703,1.018,2704,1.018,2705,0.531,2706,0.531,2707,1.018,2708,1.018,2709,0.531,2710,0.531,2711,1.018,2712,1.018,2713,1.465,2714,0.531,2715,0.531,2716,0.531,2717,1.018,2718,0.531,2719,0.531,2720,0.531,2721,0.531,2722,0.531,2723,1.018,2724,0.531,2725,0.531,2726,0.482,2727,0.531,2728,0.531,2729,0.531,2730,0.531,2731,0.531,2732,0.531]],["component/19",[11,0.17,12,0.288]],["keyword/19",[]],["title/20-1",[43,35.147,290,36.524]],["name/20-1",[]],["text/20-1",[]],["component/20-1",[]],["keyword/20-1",[]],["title/20-2",[11,12.322,117,23.916,118,25.081,392,25.508]],["name/20-2",[]],["text/20-2",[]],["component/20-2",[]],["keyword/20-2",[]],["title/20-3",[11,10.963,109,23.093,117,21.278,118,22.314,172,20.962]],["name/20-3",[]],["text/20-3",[]],["component/20-3",[]],["keyword/20-3",[]],["title/20-4",[11,9.873,117,19.164,118,20.097,199,21.178,404,20.439,534,17.845]],["name/20-4",[]],["text/20-4",[]],["component/20-4",[]],["keyword/20-4",[]],["title/20-5",[480,24.819,805,33.352]],["name/20-5",[]],["text/20-5",[]],["component/20-5",[]],["keyword/20-5",[]],["title/20-6",[480,24.819,806,37.28]],["name/20-6",[]],["text/20-6",[]],["component/20-6",[]],["keyword/20-6",[]],["title/20-7",[11,10.963,117,21.278,118,22.314,209,23.961,418,20.085]],["name/20-7",[]],["text/20-7",[]],["component/20-7",[]],["keyword/20-7",[]],["title/20-8",[752,32.297,2733,57.218]],["name/20-8",[]],["text/20-8",[]],["component/20-8",[]],["keyword/20-8",[]],["title/20-9",[11,12.322,116,27.466,480,18.664,625,29.301]],["name/20-9",[]],["text/20-9",[]],["component/20-9",[]],["keyword/20-9",[]],["title/20-10",[480,24.819,1532,38.963]],["name/20-10",[]],["text/20-10",[]],["component/20-10",[]],["keyword/20-10",[]],["title/20-11",[9,17.06,1214,31.641,1532,29.301,1571,38.015]],["name/20-11",[]],["text/20-11",[]],["component/20-11",[]],["keyword/20-11",[]],["title/20-12",[9,22.686,1571,50.551]],["name/20-12",[]],["text/20-12",[]],["component/20-12",[]],["keyword/20-12",[]],["title/20-13",[9,17.06,741,30.788,1532,29.301,2734,40.158]],["name/20-13",[]],["text/20-13",[]],["component/20-13",[]],["keyword/20-13",[]],["title/20-14",[9,22.686,2734,53.402]],["name/20-14",[]],["text/20-14",[]],["component/20-14",[]],["keyword/20-14",[]],["title/20-15",[9,17.06,1532,29.301,1665,36.303,2735,40.158]],["name/20-15",[]],["text/20-15",[]],["component/20-15",[]],["keyword/20-15",[]],["title/20-16",[9,22.686,2735,53.402]],["name/20-16",[]],["text/20-16",[]],["component/20-16",[]],["keyword/20-16",[]],["title/20-17",[0,34.26,68,30.172,752,27.725]],["name/20-17",[]],["text/20-17",[]],["component/20-17",[]],["keyword/20-17",[]],["title/20-18",[201,29.631,213,30.745,619,29.631]],["name/20-18",[]],["text/20-18",[]],["component/20-18",[]],["keyword/20-18",[]],["title/20-19",[190,24.675,201,25.957,319,25.957,582,29.301]],["name/20-19",[]],["text/20-19",[]],["component/20-19",[]],["keyword/20-19",[]],["title/20-20",[201,34.517,1299,38.963]],["name/20-20",[]],["text/20-20",[]],["component/20-20",[]],["keyword/20-20",[]],["title/20-21",[9,19.475,209,30.745,2736,49.119]],["name/20-21",[]],["text/20-21",[]],["component/20-21",[]],["keyword/20-21",[]],["title/20-22",[9,19.475,1140,25.771,2737,49.119]],["name/20-22",[]],["text/20-22",[]],["component/20-22",[]],["keyword/20-22",[]],["title/20-23",[156,21.581,175,19.461,177,27.99,184,26.969,278,21.581]],["name/20-23",[]],["text/20-23",[]],["component/20-23",[]],["keyword/20-23",[]],["title/20-24",[999,47.789]],["name/20-24",[]],["text/20-24",[]],["component/20-24",[]],["keyword/20-24",[]],["title/20-25",[400,55.538]],["name/20-25",[]],["text/20-25",[]],["component/20-25",[]],["keyword/20-25",[]],["title/20-26",[172,31.331,2738,63.012]],["name/20-26",[]],["text/20-26",[]],["component/20-26",[]],["keyword/20-26",[]],["title/20",[11,14.066,117,27.302,118,28.631]],["name/20",[2739,0.861]],["text/20",[1,0.534,6,0.572,8,1.076,9,2.616,10,1.418,11,2.17,12,0.236,13,0.913,19,0.317,21,1.226,22,1.571,25,0.368,28,3.931,29,0.935,44,1.395,46,2.679,49,2.321,51,1.35,55,1.144,61,0.368,62,0.666,66,0.348,67,1.12,68,0.572,76,3.798,77,0.429,82,1.037,83,0.274,86,0.729,89,1.32,91,0.786,95,1.562,101,2.188,102,1.016,106,0.324,107,0.454,109,0.562,111,1.583,112,1.888,113,0.368,115,0.572,116,1.9,117,4.562,118,4.931,119,0.283,120,1.002,121,1.002,123,0.874,124,0.331,125,0.959,126,0.634,127,0.65,129,1.625,131,0.454,132,0.394,137,1.677,139,1.888,142,0.348,145,0.38,147,1.443,148,0.31,150,0.324,151,0.357,154,0.634,156,1.498,159,0.31,160,0.562,161,0.357,162,0.809,166,0.394,167,0.27,168,0.331,170,1.583,172,0.941,174,0.562,175,3.071,176,0.348,177,3.318,180,0.348,184,0.38,185,0.394,190,1.186,191,2.544,192,1.23,194,2.327,195,0.839,196,0.394,197,1.981,199,0.299,201,3.574,207,0.41,209,3.963,211,0.62,212,0.755,213,3.841,217,1.226,218,1.294,221,0.368,228,0.348,231,1.23,234,0.368,237,0.856,239,0.348,242,1.794,246,0.893,256,0.65,259,0.62,260,1.625,266,1.087,268,0.348,271,0.348,273,0.786,274,0.634,275,0.65,276,1.498,278,2.028,280,0.486,283,0.607,292,0.795,296,1.764,300,0.324,303,1.951,304,0.607,305,0.357,307,1.12,310,0.893,315,0.914,317,0.339,319,2.375,320,0.429,321,0.856,323,0.331,330,0.429,337,0.368,340,0.572,345,1.33,348,1.968,349,0.348,351,2.375,355,0.394,356,0.429,358,0.634,364,0.893,366,2.734,379,0.41,380,1.45,383,1.928,384,0.394,385,0.607,392,0.288,393,1.226,394,0.874,399,0.357,404,1.019,409,0.348,411,0.607,417,1.408,418,0.902,430,0.348,431,0.41,432,0.874,433,1.302,436,0.331,441,0.324,461,0.856,463,0.38,470,1.669,471,0.394,475,0.357,476,1.12,478,0.331,479,0.317,480,2.416,481,0.331,490,0.913,499,0.685,500,1.12,504,1.294,506,0.41,512,1.939,515,0.339,516,0.339,518,0.41,523,0.666,524,1.676,526,0.666,527,0.823,530,0.935,532,0.41,533,2.273,534,1.677,535,2.318,536,2.816,537,0.31,541,0.38,549,0.38,550,1.185,553,1.17,555,0.755,557,1.759,558,1.341,565,0.429,568,0.41,569,0.666,570,0.666,571,0.65,572,0.935,573,0.65,576,0.38,580,1.559,581,0.357,582,0.634,585,0.705,593,0.368,594,1.939,595,0.357,596,0.935,601,2.103,608,0.986,613,0.685,614,0.394,616,0.394,618,0.729,619,3.282,625,0.634,641,0.666,646,0.331,648,0.935,654,0.38,656,0.454,661,0.394,665,0.62,673,1.016,691,0.394,693,0.394,695,1.131,697,0.357,706,0.31,719,0.368,721,1.377,722,0.357,726,0.429,731,1.016,732,0.357,733,0.913,737,0.429,738,0.429,739,0.823,741,1.23,745,1.604,746,1.93,747,2.206,749,0.454,750,0.454,752,2.995,753,1.291,754,3.556,755,1.23,763,1.442,776,0.357,805,0.543,806,0.607,819,0.394,820,0.41,831,0.38,832,0.429,835,0.339,851,0.38,853,0.38,860,0.38,869,0.394,886,0.913,900,0.324,901,4.54,904,0.454,921,1.049,925,0.38,928,0.429,931,4.599,932,2.157,940,0.429,949,0.357,954,0.913,955,2.382,958,0.41,963,1.408,971,0.893,979,1.9,982,0.786,999,1.199,1007,0.348,1012,0.331,1020,0.705,1023,0.705,1024,0.368,1034,0.634,1042,0.893,1056,0.339,1073,0.41,1077,0.357,1080,0.357,1084,0.41,1086,0.38,1089,0.454,1090,0.454,1099,0.666,1103,0.394,1106,0.348,1107,0.429,1113,0.394,1121,3.024,1124,0.705,1125,0.454,1138,0.38,1140,0.703,1148,0.755,1151,0.454,1152,3.078,1160,2.321,1162,1.23,1167,0.368,1170,1.087,1172,0.38,1173,0.429,1174,0.454,1175,0.685,1182,0.368,1184,2.019,1185,1.939,1186,0.41,1188,1.618,1189,0.348,1190,0.368,1203,0.755,1206,0.41,1207,0.429,1214,3.046,1215,0.394,1221,1.049,1268,0.429,1293,0.755,1297,1.302,1299,2.374,1307,0.454,1311,1.251,1312,0.823,1319,0.869,1322,0.394,1325,0.454,1344,1.131,1345,0.823,1356,0.429,1364,0.486,1368,0.41,1389,0.429,1397,2.412,1401,0.755,1421,2.114,1423,0.454,1466,1.185,1470,0.41,1484,2.233,1485,1.251,1493,0.454,1512,0.486,1513,0.454,1514,0.486,1516,0.486,1532,3.705,1538,0.786,1544,0.486,1560,0.869,1571,0.429,1585,0.429,1594,0.454,1608,0.931,1611,0.869,1612,0.486,1628,0.486,1630,0.429,1635,0.454,1636,1.087,1639,0.41,1652,0.755,1664,0.486,1665,2.942,1687,0.454,1693,0.429,1699,0.429,1701,0.394,1709,0.869,1713,0.41,1718,0.486,1719,0.869,1720,1.131,1721,0.486,1728,0.454,1731,0.454,1734,1.45,1743,0.869,1744,0.454,1759,0.486,1765,1.518,1767,0.454,1772,0.41,1781,0.429,1786,0.823,1788,1.251,1814,0.454,1817,0.486,1834,0.429,1847,0.823,1856,0.486,1857,0.869,1877,0.486,1896,0.454,1926,0.41,1937,0.454,1958,0.486,1959,0.486,1964,0.869,1980,0.429,2050,0.869,2051,0.429,2123,0.486,2160,0.454,2188,1.719,2189,0.486,2202,0.931,2235,0.454,2276,0.454,2281,0.869,2372,0.429,2388,0.486,2401,0.454,2434,0.486,2450,0.486,2488,0.486,2493,0.429,2494,0.429,2527,1.341,2539,0.454,2584,0.454,2644,0.931,2654,0.486,2697,0.486,2699,2.068,2726,1.341,2733,2.393,2734,0.454,2735,0.454,2736,1.719,2737,1.719,2740,0.535,2741,0.535,2742,0.535,2743,1.026,2744,0.535,2745,1.477,2746,0.535,2747,1.477,2748,1.026,2749,0.535,2750,0.535,2751,0.535,2752,0.535,2753,0.535,2754,0.535,2755,1.893,2756,1.477,2757,2.635,2758,0.535,2759,1.026,2760,3.84,2761,1.893,2762,0.535,2763,0.535,2764,1.026,2765,0.535,2766,0.535,2767,1.893,2768,0.535,2769,0.535,2770,0.535,2771,1.026,2772,1.026,2773,0.535,2774,0.535,2775,0.535,2776,0.535,2777,0.535,2778,1.026,2779,1.026,2780,0.535,2781,0.535,2782,0.535,2783,0.535,2784,0.535,2785,0.535,2786,0.535,2787,0.535,2788,0.535,2789,0.535,2790,0.535,2791,0.535,2792,0.535,2793,0.535,2794,0.535,2795,0.535,2796,0.535,2797,0.535,2798,0.535,2799,0.535,2800,0.535,2801,0.535,2802,0.535,2803,0.535,2804,1.026,2805,1.026,2806,1.477,2807,0.535,2808,1.893,2809,0.535,2810,0.535,2811,0.535,2812,0.535,2813,0.535,2814,1.026,2815,0.535,2816,0.535,2817,0.535,2818,0.535,2819,0.535,2820,0.535,2821,1.026,2822,0.535,2823,0.535,2824,0.535,2825,0.535,2826,0.535,2827,0.535,2828,1.893,2829,0.535,2830,0.535,2831,0.535,2832,0.535,2833,0.535,2834,0.535,2835,0.535,2836,0.535,2837,0.535,2838,0.535,2839,0.535,2840,0.535,2841,0.535,2842,1.026,2843,0.535,2844,0.535,2845,0.535,2846,0.535,2847,0.535,2848,0.535,2849,0.535,2850,0.535,2851,0.535,2852,0.535,2853,0.535,2854,0.535,2855,0.535,2856,0.535,2857,0.535,2858,0.535,2859,0.535,2860,0.535,2861,0.535]],["component/20",[11,0.17,12,0.288]],["keyword/20",[]]],"invertedIndex":[["",{"_index":901,"title":{"19-13":{},"19-15":{}},"name":{},"text":{"4":{},"5":{},"6":{},"7":{},"8":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["0.0",{"_index":2471,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["0.002270643",{"_index":2129,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["0.5",{"_index":2248,"title":{},"name":{},"text":{"17":{},"18":{}},"component":{},"keyword":{}}],["0.7",{"_index":2207,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["00",{"_index":815,"title":{},"name":{"4":{}},"text":{},"component":{},"keyword":{}}],["00:00:00",{"_index":2716,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["01",{"_index":839,"title":{},"name":{"5":{}},"text":{"4":{},"11":{},"19":{}},"component":{},"keyword":{}}],["02",{"_index":909,"title":{},"name":{"6":{}},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["03",{"_index":1143,"title":{},"name":{"7":{}},"text":{},"component":{},"keyword":{}}],["04",{"_index":1274,"title":{},"name":{"8":{}},"text":{},"component":{},"keyword":{}}],["05",{"_index":1310,"title":{},"name":{"9":{}},"text":{},"component":{},"keyword":{}}],["06",{"_index":1330,"title":{},"name":{"10":{}},"text":{},"component":{},"keyword":{}}],["1",{"_index":270,"title":{"18-9":{}},"name":{},"text":{"2":{},"3":{},"12":{},"13":{},"16":{},"17":{}},"component":{},"keyword":{}}],["1.0",{"_index":1645,"title":{},"name":{},"text":{"13":{},"18":{}},"component":{},"keyword":{}}],["1.10.13",{"_index":920,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["1.9.20",{"_index":915,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["10",{"_index":599,"title":{"12":{},"12-3":{},"12-4":{}},"name":{},"text":{"3":{},"6":{},"11":{},"12":{},"17":{}},"component":{},"keyword":{}}],["10).thi",{"_index":2366,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["100",{"_index":1999,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["1000",{"_index":2287,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["100mb",{"_index":2001,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["1019",{"_index":894,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["1024",{"_index":2000,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["11",{"_index":852,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["1234",{"_index":2538,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["12345",{"_index":2792,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["1299.0",{"_index":1441,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["14",{"_index":1542,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["14250:14250",{"_index":2482,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["14268:14268",{"_index":2481,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["15",{"_index":1196,"title":{},"name":{},"text":{"7":{},"11":{},"12":{},"14":{},"15":{}},"component":{},"keyword":{}}],["15\",\"id\":1,\"name\":\"iphone\",\"price\":999.99},{\"description\":\"appl",{"_index":1270,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["15–30",{"_index":2724,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["16",{"_index":840,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["16686:16686",{"_index":2480,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["16:47:16",{"_index":910,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["17",{"_index":850,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["17.0.10",{"_index":838,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["17.0.10+7",{"_index":843,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["1735686000",{"_index":2610,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["1735689600",{"_index":2608,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["1l",{"_index":1979,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["1rzgn5tiysnmztrof869lq",{"_index":2625,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["2",{"_index":657,"title":{"18-10":{}},"name":{},"text":{"3":{},"15":{},"17":{}},"component":{},"keyword":{}}],["2.0",{"_index":75,"title":{},"name":{},"text":{"2":{},"19":{}},"component":{},"keyword":{}}],["2.1",{"_index":2732,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["200",{"_index":1624,"title":{},"name":{},"text":{"13":{},"14":{}},"component":{},"keyword":{}}],["2000",{"_index":2196,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["201",{"_index":1535,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["2016",{"_index":420,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["2023",{"_index":924,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["2024",{"_index":59,"title":{},"name":{},"text":{"2":{},"3":{},"4":{},"11":{}},"component":{},"keyword":{}}],["2024.1",{"_index":945,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["2025",{"_index":2715,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["204",{"_index":1561,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["3",{"_index":1489,"title":{"18-11":{}},"name":{},"text":{"12":{},"15":{},"17":{}},"component":{},"keyword":{}}],["3\",\"name\":\"iphone14",{"_index":1562,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["3.0",{"_index":2135,"title":{},"name":{},"text":{"16":{},"19":{}},"component":{},"keyword":{}}],["3.0.17",{"_index":917,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["3.0.3",{"_index":1643,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["3.0jakarta.jakartae",{"_index":1055,"title":{},"name":{},"text":{"5":{},"6":{},"11":{}},"component":{},"keyword":{}}],["artifactid>junit",{"_index":1066,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["artifactid>liberti",{"_index":1252,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["artifactid>lombokmaven",{"_index":1248,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["artifactid>microprofil",{"_index":1106,"title":{},"name":{},"text":{"6":{},"11":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["artifactid>microprofilemp",{"_index":1037,"title":{},"name":{},"text":{"5":{},"7":{},"11":{}},"component":{},"keyword":{}}],["artifactid>opentelemetri",{"_index":1131,"title":{},"name":{},"text":{"6":{},"11":{}},"component":{},"keyword":{}}],["ascend",{"_index":2810,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["asciidoc",{"_index":14,"title":{},"name":{},"text":{"1":{}},"component":{},"keyword":{}}],["aspect",{"_index":165,"title":{},"name":{},"text":{"2":{},"5":{},"6":{},"11":{},"12":{},"14":{},"15":{},"16":{}},"component":{},"keyword":{}}],["assert",{"_index":1292,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["assertequals(2",{"_index":1290,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["assertnotnull(product",{"_index":1289,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["assess",{"_index":1300,"title":{},"name":{},"text":{"8":{},"11":{},"15":{}},"component":{},"keyword":{}}],["assign",{"_index":2694,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["assist",{"_index":936,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["associ",{"_index":355,"title":{},"name":{},"text":{"2":{},"3":{},"6":{},"11":{},"18":{},"20":{}},"component":{},"keyword":{}}],["assum",{"_index":293,"title":{},"name":{},"text":{"2":{},"7":{},"11":{},"14":{}},"component":{},"keyword":{}}],["asynchron",{"_index":613,"title":{"17-4":{},"17-28":{},"17-29":{},"17-32":{},"17-33":{}},"name":{},"text":{"3":{},"13":{},"17":{},"20":{}},"component":{},"keyword":{}}],["atlanta",{"_index":444,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["attach",{"_index":2434,"title":{},"name":{},"text":{"18":{},"20":{}},"component":{},"keyword":{}}],["attack",{"_index":2713,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["attempt",{"_index":781,"title":{},"name":{},"text":{"3":{},"14":{},"15":{},"17":{}},"component":{},"keyword":{}}],["attent",{"_index":1883,"title":{},"name":{},"text":{"14":{},"18":{}},"component":{},"keyword":{}}],["attribut",{"_index":1755,"title":{"18-12":{}},"name":{},"text":{"14":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["aud",{"_index":2611,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["audienc",{"_index":2612,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["audit",{"_index":1888,"title":{"18-40":{}},"name":{},"text":{"14":{},"18":{},"19":{}},"component":{},"keyword":{}}],["augment",{"_index":1605,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["auth",{"_index":985,"title":{},"name":{},"text":{"4":{},"6":{},"11":{},"19":{}},"component":{},"keyword":{}}],["authent",{"_index":307,"title":{"19":{},"19-2":{},"19-9":{},"19-41":{}},"name":{},"text":{"2":{},"3":{},"6":{},"11":{},"13":{},"14":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["authentication—th",{"_index":2857,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["author",{"_index":13,"title":{"19-10":{},"19-17":{}},"name":{},"text":{"1":{},"3":{},"4":{},"11":{},"13":{},"14":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["authtoken",{"_index":2790,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["auto",{"_index":1070,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["autoclos",{"_index":2767,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["autom",{"_index":585,"title":{},"name":{},"text":{"3":{},"8":{},"11":{},"13":{},"14":{},"15":{},"18":{},"20":{}},"component":{},"keyword":{}}],["automat",{"_index":1121,"title":{"18-23":{}},"name":{},"text":{"6":{},"11":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["autonom",{"_index":2153,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["avail",{"_index":245,"title":{},"name":{},"text":{"2":{},"3":{},"6":{},"7":{},"11":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["availablememori",{"_index":1997,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["averag",{"_index":2090,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["avoid",{"_index":516,"title":{"17-21":{},"18-37":{}},"name":{},"text":{"3":{},"4":{},"9":{},"11":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["aw",{"_index":1272,"title":{},"name":{},"text":{"7":{},"11":{},"14":{},"19":{}},"component":{},"keyword":{}}],["away",{"_index":1910,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["axy8dctdaglsbgljb3rozq",{"_index":2626,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["azul",{"_index":857,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["azur",{"_index":895,"title":{},"name":{},"text":{"4":{},"7":{},"11":{},"14":{}},"component":{},"keyword":{}}],["b",{"_index":320,"title":{},"name":{},"text":{"2":{},"3":{},"12":{},"20":{}},"component":{},"keyword":{}}],["back",{"_index":511,"title":{},"name":{},"text":{"3":{},"16":{}},"component":{},"keyword":{}}],["backend",{"_index":2448,"title":{},"name":{},"text":{"18":{},"19":{}},"component":{},"keyword":{}}],["background",{"_index":291,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["balanc",{"_index":795,"title":{},"name":{},"text":{"3":{},"15":{},"18":{}},"component":{},"keyword":{}}],["bandwidth",{"_index":2344,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["base",{"_index":112,"title":{"19-10":{},"19-11":{},"19-29":{}},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["base64",{"_index":2595,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["baselin",{"_index":690,"title":{},"name":{},"text":{"3":{},"18":{}},"component":{},"keyword":{}}],["baseuri",{"_index":2851,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["baseuri(productapiuri",{"_index":2840,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["basi",{"_index":88,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["basic",{"_index":294,"title":{},"name":{},"text":{"2":{},"5":{},"8":{},"11":{},"12":{},"14":{},"15":{},"16":{},"18":{}},"component":{},"keyword":{}}],["batch",{"_index":2450,"title":{},"name":{},"text":{"18":{},"20":{}},"component":{},"keyword":{}}],["bc0240f3c744dd6b6ec2920b3cd08dcc295161a",{"_index":882,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["be",{"_index":666,"title":{},"name":{},"text":{"3":{},"8":{},"11":{},"15":{},"16":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["bean",{"_index":1344,"title":{"12-20":{}},"name":{},"text":{"12":{},"14":{},"15":{},"20":{}},"component":{},"keyword":{}}],["beanparam",{"_index":2798,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["bearer",{"_index":2644,"title":{},"name":{},"text":{"19":{},"20":{}},"component":{},"keyword":{}}],["becom",{"_index":431,"title":{},"name":{},"text":{"3":{},"16":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["befor",{"_index":1080,"title":{},"name":{},"text":{"6":{},"8":{},"11":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["beforeeach",{"_index":1283,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["begin",{"_index":820,"title":{},"name":{},"text":{"4":{},"6":{},"11":{},"18":{},"20":{}},"component":{},"keyword":{}}],["beginn",{"_index":135,"title":{},"name":{},"text":{"2":{},"12":{}},"component":{},"keyword":{}}],["behav",{"_index":1305,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["behavior",{"_index":731,"title":{},"name":{},"text":{"3":{},"12":{},"14":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["behind",{"_index":1361,"title":{},"name":{},"text":{"12":{},"16":{},"18":{}},"component":{},"keyword":{}}],["below",{"_index":174,"title":{},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"8":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["benefici",{"_index":2488,"title":{},"name":{},"text":{"18":{},"20":{}},"component":{},"keyword":{}}],["benefit",{"_index":399,"title":{"3-10":{},"19-14":{}},"name":{},"text":{"3":{},"6":{},"11":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["besid",{"_index":1606,"title":{},"name":{},"text":{"13":{},"16":{}},"component":{},"keyword":{}}],["best",{"_index":667,"title":{"14-19":{},"15-22":{},"17-20":{},"17-27":{},"17-32":{},"17-36":{},"17-42":{},"17-51":{},"18-35":{},"19-41":{},"19-42":{}},"name":{},"text":{"3":{},"4":{},"6":{},"11":{},"12":{},"14":{},"15":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["better",{"_index":697,"title":{},"name":{},"text":{"3":{},"7":{},"11":{},"12":{},"13":{},"16":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["between",{"_index":476,"title":{},"name":{},"text":{"3":{},"6":{},"7":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["beyond",{"_index":1793,"title":{},"name":{},"text":{"14":{},"16":{},"18":{},"19":{}},"component":{},"keyword":{}}],["bigdecim",{"_index":1811,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["binari",{"_index":1510,"title":{},"name":{},"text":{"12":{},"15":{}},"component":{},"keyword":{}}],["bind",{"_index":533,"title":{"12-13":{},"12-14":{}},"name":{},"text":{"3":{},"12":{},"20":{}},"component":{},"keyword":{}}],["bit",{"_index":845,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["block",{"_index":550,"title":{},"name":{},"text":{"3":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["bodi",{"_index":1485,"title":{},"name":{},"text":{"12":{},"13":{},"20":{}},"component":{},"keyword":{}}],["boilerpl",{"_index":524,"title":{},"name":{},"text":{"3":{},"4":{},"5":{},"11":{},"12":{},"20":{}},"component":{},"keyword":{}}],["boldfac",{"_index":347,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["bomjsonb",{"_index":1232,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["feature>mpconfig",{"_index":1841,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["feature>restfulw",{"_index":1230,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["featuremanag",{"_index":1229,"title":{},"name":{},"text":{"7":{},"11":{},"14":{}},"component":{},"keyword":{}}],["feder",{"_index":2575,"title":{"19-13":{}},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["fetch",{"_index":186,"title":{},"name":{},"text":{"2":{},"6":{},"9":{},"11":{},"12":{},"14":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["fetchdata",{"_index":2273,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["few",{"_index":2177,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["field",{"_index":1167,"title":{},"name":{},"text":{"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"20":{}},"component":{},"keyword":{}}],["figur",{"_index":269,"title":{},"name":{},"text":{"2":{},"3":{}},"component":{},"keyword":{}}],["fik51vwhsxj",{"_index":2629,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["file",{"_index":8,"title":{},"name":{},"text":{"1":{},"2":{},"4":{},"5":{},"7":{},"8":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["file(",{"_index":65,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["filenam",{"_index":391,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["filesystem",{"_index":1871,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["fill",{"_index":493,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["filter",{"_index":1611,"title":{},"name":{},"text":{"13":{},"18":{},"20":{}},"component":{},"keyword":{}}],["final",{"_index":409,"title":{},"name":{},"text":{"3":{},"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["finalname>${project.artifactid}io.microprofile.tutorialio.openliberty.toolsio.opentelemetryjakarta.platformorg.apache.maven.pluginsorg.eclipse.microprofile.configorg.eclipse.microprofile.fault",{"_index":1115,"title":{},"name":{},"text":{"6":{},"11":{},"17":{}},"component":{},"keyword":{}}],["groupid>org.eclipse.microprofile.healthorg.eclipse.microprofile.jwtorg.eclipse.microprofile.metricsorg.eclipse.microprofile.openapiorg.eclipse.microprofile.rest.clientorg.eclipse.microprofile.telemetryorg.eclipse.microprofileorg.junit.jupiterorg.projectlombok..${liberty.var.default.http.port}//api/product",{"_index":1220,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["http://localhost:/openapi",{"_index":1638,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["http://maven.apache.org/xsd/maven",{"_index":1030,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["http://www.apache.org/licenses/licens",{"_index":80,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["httpendpoint",{"_index":1234,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["httpget",{"_index":2040,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["httpport=\"${default.http.port",{"_index":1235,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["https://:16686",{"_index":2486,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["https://api.paymentgateway.com",{"_index":1785,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["https://docs.gradle.org/8.6/releas",{"_index":907,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["https://example.com/issu",{"_index":2695,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["https://github.com/eclipse/microprofil",{"_index":1308,"title":{},"name":{},"text":{"8":{},"11":{},"15":{}},"component":{},"keyword":{}}],["https://io.microprofile.com/issu",{"_index":2605,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["https://localhost:/metrics?scope=appl",{"_index":2126,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["https://microprofile.io",{"_index":1306,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["httpsport=\"${default.https.port",{"_index":1236,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["human",{"_index":1500,"title":{},"name":{},"text":{"12":{},"13":{},"15":{},"17":{}},"component":{},"keyword":{}}],["hypertext",{"_index":1523,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["iat",{"_index":2609,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["ibm",{"_index":449,"title":{},"name":{},"text":{"3":{},"4":{},"11":{}},"component":{},"keyword":{}}],["id",{"_index":932,"title":{"5-2":{},"11-21":{}},"name":{},"text":{"4":{},"5":{},"7":{},"10":{},"11":{},"12":{},"13":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["id).build",{"_index":2704,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["id=\"defaulthttpendpoint",{"_index":1237,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["idea",{"_index":809,"title":{"4-10":{},"11-10":{}},"name":{},"text":{"4":{},"10":{},"11":{}},"component":{},"keyword":{}}],["ideal",{"_index":982,"title":{},"name":{},"text":{"4":{},"11":{},"12":{},"18":{},"20":{}},"component":{},"keyword":{}}],["ident",{"_index":1118,"title":{"19-11":{}},"name":{},"text":{"6":{},"11":{},"14":{},"19":{}},"component":{},"keyword":{}}],["identif",{"_index":2458,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["identifi",{"_index":974,"title":{"18-31":{}},"name":{},"text":{"4":{},"5":{},"7":{},"8":{},"11":{},"12":{},"14":{},"16":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["ijug",{"_index":447,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["illumin",{"_index":2145,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["illustr",{"_index":1613,"title":{},"name":{},"text":{"13":{},"15":{},"16":{}},"component":{},"keyword":{}}],["imag",{"_index":1218,"title":{},"name":{},"text":{"7":{},"11":{},"15":{}},"component":{},"keyword":{}}],["immedi",{"_index":1085,"title":{},"name":{},"text":{"6":{},"11":{},"15":{},"17":{}},"component":{},"keyword":{}}],["impact",{"_index":1882,"title":{},"name":{},"text":{"14":{},"15":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["implement",{"_index":170,"title":{"3-8":{},"12-23":{},"14-8":{},"15-13":{},"15-17":{},"15-18":{},"17-14":{},"17-30":{},"17-37":{}},"name":{},"text":{"2":{},"3":{},"4":{},"6":{},"7":{},"8":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["impli",{"_index":94,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["implic",{"_index":2530,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["import",{"_index":931,"title":{},"name":{},"text":{"4":{},"6":{},"7":{},"8":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["improv",{"_index":470,"title":{},"name":{},"text":{"3":{},"4":{},"6":{},"11":{},"12":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["in_progress",{"_index":2424,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["inbound",{"_index":1924,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["incid",{"_index":2560,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["includ",{"_index":160,"title":{},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"8":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["inclus",{"_index":2570,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["incom",{"_index":1926,"title":{},"name":{},"text":{"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["inconsist",{"_index":2394,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["incorpor",{"_index":546,"title":{},"name":{},"text":{"3":{},"12":{},"16":{}},"component":{},"keyword":{}}],["increas",{"_index":522,"title":{},"name":{},"text":{"3":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["increment",{"_index":872,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["indefinit",{"_index":2166,"title":{},"name":{},"text":{"17":{},"18":{}},"component":{},"keyword":{}}],["independ",{"_index":482,"title":{},"name":{},"text":{"3":{},"12":{},"19":{}},"component":{},"keyword":{}}],["index",{"_index":52,"title":{},"name":{"2":{},"18":{}},"text":{"18":{}},"component":{},"keyword":{}}],["indic",{"_index":349,"title":{},"name":{},"text":{"2":{},"5":{},"11":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["individu",{"_index":424,"title":{"6-3":{},"11-24":{}},"name":{},"text":{"3":{},"4":{},"6":{},"11":{},"15":{},"17":{},"19":{}},"component":{},"keyword":{}}],["industri",{"_index":421,"title":{},"name":{},"text":{"3":{},"13":{},"19":{}},"component":{},"keyword":{}}],["ineffici",{"_index":2461,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["inevit",{"_index":2161,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["inf",{"_index":1707,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["inf/microprofil",{"_index":1635,"title":{},"name":{},"text":{"13":{},"18":{},"20":{}},"component":{},"keyword":{}}],["inf/publickey.pem",{"_index":2667,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["inf/services/org.eclipse.microprofile.config.spi.configsourc",{"_index":1799,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["infinit",{"_index":1920,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["influenc",{"_index":1354,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["info",{"_index":1644,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["inform",{"_index":69,"title":{"19-12":{}},"name":{},"text":{"2":{},"3":{},"5":{},"6":{},"7":{},"8":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["infrastructur",{"_index":771,"title":{"18-43":{}},"name":{},"text":{"3":{},"18":{}},"component":{},"keyword":{}}],["ingest",{"_index":2449,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["inher",{"_index":663,"title":{},"name":{},"text":{"3":{},"18":{}},"component":{},"keyword":{}}],["init",{"_index":906,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["initi",{"_index":211,"title":{},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"7":{},"8":{},"11":{},"12":{},"13":{},"14":{},"15":{},"19":{},"20":{}},"component":{},"keyword":{}}],["initialdelaysecond",{"_index":2041,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["inject",{"_index":535,"title":{"12-7":{},"12-8":{}},"name":{},"text":{"3":{},"12":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["innov",{"_index":507,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["input",{"_index":1006,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["insert",{"_index":2775,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["insid",{"_index":1664,"title":{},"name":{},"text":{"13":{},"20":{}},"component":{},"keyword":{}}],["insight",{"_index":1403,"title":{},"name":{},"text":{"12":{},"15":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["inspect",{"_index":2402,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["instal",{"_index":807,"title":{"4-6":{},"11-6":{}},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["instanc",{"_index":1034,"title":{},"name":{},"text":{"5":{},"7":{},"10":{},"11":{},"12":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["instantan",{"_index":2083,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["instanti",{"_index":1174,"title":{},"name":{},"text":{"7":{},"11":{},"20":{}},"component":{},"keyword":{}}],["instead",{"_index":1720,"title":{},"name":{},"text":{"14":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["instruct",{"_index":236,"title":{},"name":{},"text":{"2":{},"4":{},"5":{},"7":{},"11":{},"18":{}},"component":{},"keyword":{}}],["instrument",{"_index":2061,"title":{"16-11":{},"18-8":{},"18-23":{},"18-24":{},"18-28":{}},"name":{},"text":{"16":{},"18":{}},"component":{},"keyword":{}}],["insuffici",{"_index":2587,"title":{"19-40":{}},"name":{},"text":{},"component":{},"keyword":{}}],["int",{"_index":1762,"title":{},"name":{},"text":{"14":{},"17":{}},"component":{},"keyword":{}}],["int64",{"_index":1653,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["integ",{"_index":1654,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["integr",{"_index":22,"title":{"4-8":{},"11-8":{},"15-19":{}},"name":{},"text":{"1":{},"2":{},"3":{},"4":{},"6":{},"7":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["intellig",{"_index":935,"title":{},"name":{},"text":{"4":{},"11":{},"18":{}},"component":{},"keyword":{}}],["intellij",{"_index":808,"title":{"4-10":{},"11-10":{}},"name":{},"text":{"4":{},"10":{},"11":{}},"component":{},"keyword":{}}],["intend",{"_index":2053,"title":{},"name":{},"text":{"15":{},"19":{}},"component":{},"keyword":{}}],["intent",{"_index":1360,"title":{},"name":{},"text":{"12":{},"16":{}},"component":{},"keyword":{}}],["interact",{"_index":231,"title":{},"name":{},"text":{"2":{},"3":{},"5":{},"9":{},"11":{},"12":{},"13":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["intercept",{"_index":1357,"title":{},"name":{},"text":{"12":{},"19":{}},"component":{},"keyword":{}}],["interceptor",{"_index":1343,"title":{"12-9":{},"12-10":{}},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["interest",{"_index":1402,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["interfac",{"_index":209,"title":{"15-14":{},"20-7":{},"20-21":{}},"name":{},"text":{"2":{},"3":{},"7":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["intermediari",{"_index":2447,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["intermitt",{"_index":2193,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["intern",{"_index":767,"title":{},"name":{},"text":{"3":{},"16":{}},"component":{},"keyword":{}}],["internet",{"_index":1338,"title":{},"name":{},"text":{"10":{},"11":{},"18":{}},"component":{},"keyword":{}}],["interoper",{"_index":514,"title":{"19-16":{}},"name":{},"text":{"3":{},"10":{},"11":{},"13":{},"19":{}},"component":{},"keyword":{}}],["interpret",{"_index":1916,"title":{},"name":{},"text":{"15":{},"16":{}},"component":{},"keyword":{}}],["interpreter/load",{"_index":1332,"title":{},"name":{},"text":{"10":{},"11":{}},"component":{},"keyword":{}}],["interruptedexcept",{"_index":2270,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["interv",{"_index":1879,"title":{},"name":{},"text":{"14":{},"16":{}},"component":{},"keyword":{}}],["intervent",{"_index":1864,"title":{},"name":{},"text":{"14":{},"17":{},"18":{}},"component":{},"keyword":{}}],["intric",{"_index":2526,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["intricaci",{"_index":316,"title":{},"name":{},"text":{"2":{},"9":{},"11":{},"12":{},"16":{}},"component":{},"keyword":{}}],["introduc",{"_index":471,"title":{},"name":{},"text":{"3":{},"13":{},"14":{},"15":{},"17":{},"20":{}},"component":{},"keyword":{}}],["introduct",{"_index":392,"title":{"3":{},"3-1":{},"4-1":{},"11-1":{},"12-1":{},"13-1":{},"13-4":{},"15-1":{},"16-1":{},"16-3":{},"18-2":{},"19-2":{},"20-2":{}},"name":{},"text":{"13":{},"15":{},"16":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["introductori",{"_index":403,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["intrus",{"_index":2514,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["intuit",{"_index":1381,"title":{},"name":{},"text":{"12":{},"13":{}},"component":{},"keyword":{}}],["invalid",{"_index":1900,"title":{"19-37":{}},"name":{},"text":{"14":{},"19":{}},"component":{},"keyword":{}}],["invalu",{"_index":1370,"title":{},"name":{},"text":{"12":{},"16":{}},"component":{},"keyword":{}}],["invas",{"_index":1369,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["inventori",{"_index":184,"title":{"20-23":{}},"name":{},"text":{"2":{},"9":{},"11":{},"17":{},"19":{},"20":{}},"component":{},"keyword":{}}],["inventoryservic",{"_index":1319,"title":{},"name":{},"text":{"9":{},"11":{},"20":{}},"component":{},"keyword":{}}],["investig",{"_index":2565,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["invoc",{"_index":1124,"title":{"16-13":{}},"name":{},"text":{"6":{},"11":{},"12":{},"15":{},"16":{},"17":{},"20":{}},"component":{},"keyword":{}}],["invok",{"_index":595,"title":{},"name":{},"text":{"3":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["involv",{"_index":722,"title":{},"name":{},"text":{"3":{},"10":{},"11":{},"14":{},"15":{},"16":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["io",{"_index":1011,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["io.microprofil",{"_index":977,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["io.microprofile.tutori",{"_index":998,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.inventory.cli",{"_index":2755,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.inventory.dto.product",{"_index":2758,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.inventory.dto.productnotfoundexcept",{"_index":2823,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.stor",{"_index":1328,"title":{},"name":{},"text":{"9":{},"11":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.inventory.client.productservicecli",{"_index":2831,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.inventory.servic",{"_index":2830,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.config",{"_index":1775,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.ent",{"_index":1802,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.entity.paymentdetail",{"_index":2282,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.except",{"_index":2214,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.exception.paymentprocessingexcept",{"_index":2283,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.servic",{"_index":2194,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.paymentservice/processpayment/retry/delay=2000",{"_index":2228,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.paymentservice/processpayment/retry/jitter=500",{"_index":2229,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.paymentservice/processpayment/retry/maxretries=3",{"_index":2227,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.productservice/fetchdata/timeout/value=1500",{"_index":2274,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.productservice/fetchproductdetails/circuitbreaker/delay=5000",{"_index":2256,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.productservice/fetchproductdetails/circuitbreaker/failureratio=0.5",{"_index":2255,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.productservice/fetchproductdetails/circuitbreaker/requestvolumethreshold=10",{"_index":2254,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.payment.service.productservice/fetchproductdetails/circuitbreaker/successthreshold=2",{"_index":2257,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product",{"_index":1210,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.cache.productcach",{"_index":2322,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.ent",{"_index":1155,"title":{},"name":{},"text":{"7":{},"11":{},"12":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.entity.product",{"_index":1182,"title":{},"name":{},"text":{"7":{},"8":{},"11":{},"12":{},"14":{},"15":{},"17":{},"20":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.health",{"_index":1971,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.repositori",{"_index":1430,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.repository.productrepositori",{"_index":1474,"title":{},"name":{},"text":{"12":{},"14":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.resourc",{"_index":1179,"title":{},"name":{},"text":{"7":{},"8":{},"11":{},"12":{},"14":{}},"component":{},"keyword":{}}],["io.microprofile.tutorial.store.product.service.productservice.timeout=3000",{"_index":2338,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["io.opentelemetry.api.trace.span",{"_index":2417,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["io.opentelemetry.api.trace.trac",{"_index":2416,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["io.opentelemetry.instrumentation.annotations.withspan",{"_index":2497,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["iphon",{"_index":1194,"title":{},"name":{},"text":{"7":{},"11":{},"12":{},"14":{}},"component":{},"keyword":{}}],["ipkxlg",{"_index":2618,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["is`nul",{"_index":1752,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["isdatabaseconnectionhealthi",{"_index":1976,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["isol",{"_index":686,"title":{"17-44":{},"17-45":{},"17-46":{},"17-51":{},"18-43":{}},"name":{},"text":{"3":{},"14":{},"17":{},"18":{}},"component":{},"keyword":{}}],["isproductavail",{"_index":2848,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["isproductavailable(long",{"_index":2835,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["iss",{"_index":2604,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["issu",{"_index":779,"title":{},"name":{},"text":{"3":{},"6":{},"8":{},"11":{},"14":{},"16":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["issuanc",{"_index":2714,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["issuer",{"_index":2632,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["istio",{"_index":652,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["ital",{"_index":375,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["item",{"_index":203,"title":{},"name":{},"text":{"2":{},"12":{},"13":{},"16":{}},"component":{},"keyword":{}}],["itself",{"_index":1389,"title":{},"name":{},"text":{"12":{},"14":{},"16":{},"20":{}},"component":{},"keyword":{}}],["it’",{"_index":644,"title":{},"name":{},"text":{"3":{},"4":{},"6":{},"8":{},"11":{},"14":{},"15":{},"16":{}},"component":{},"keyword":{}}],["jackson",{"_index":1169,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["jaeger",{"_index":2375,"title":{"18-16":{},"18-21":{}},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["jaegertracing/al",{"_index":2484,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["jaeger’",{"_index":2453,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["jakarta",{"_index":303,"title":{"12":{},"3-11":{},"12-3":{},"12-4":{},"12-5":{},"12-7":{},"12-9":{},"12-10":{},"12-11":{},"12-12":{},"12-13":{},"12-14":{},"12-15":{},"12-16":{},"12-20":{}},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"11":{},"12":{},"13":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ejb.startup",{"_index":2017,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["jakarta.enterprise.context.applicationscop",{"_index":1183,"title":{},"name":{},"text":{"7":{},"11":{},"12":{},"14":{},"15":{},"17":{},"18":{}},"component":{},"keyword":{}}],["jakarta.enterprise.context.requestscop",{"_index":1431,"title":{},"name":{},"text":{"12":{},"14":{},"17":{}},"component":{},"keyword":{}}],["jakarta.inject.inject",{"_index":1475,"title":{},"name":{},"text":{"12":{},"14":{},"17":{},"18":{}},"component":{},"keyword":{}}],["jakarta.persistence.ent",{"_index":1410,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jakarta.persistence.entitymanag",{"_index":1432,"title":{},"name":{},"text":{"12":{},"15":{}},"component":{},"keyword":{}}],["jakarta.persistence.entitymanagerfactori",{"_index":2018,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["jakarta.persistence.generatedvalu",{"_index":1412,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jakarta.persistence.id",{"_index":1411,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jakarta.persistence.persistencecontext",{"_index":1433,"title":{},"name":{},"text":{"12":{},"15":{}},"component":{},"keyword":{}}],["jakarta.persistence.persistenceunit",{"_index":2019,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["jakarta.transaction.transact",{"_index":1724,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["jakarta.validation.constraints.notnul",{"_index":1413,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jakarta.ws.r",{"_index":1476,"title":{},"name":{},"text":{"12":{},"14":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.applicationpath",{"_index":1211,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.consum",{"_index":1817,"title":{},"name":{},"text":{"14":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.core.appl",{"_index":1212,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.core.mediatyp",{"_index":1187,"title":{},"name":{},"text":{"7":{},"11":{},"12":{},"13":{},"14":{},"17":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.core.respons",{"_index":1719,"title":{},"name":{},"text":{"14":{},"17":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.get",{"_index":1184,"title":{},"name":{},"text":{"7":{},"11":{},"13":{},"18":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.headerparam",{"_index":2786,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.path",{"_index":1185,"title":{},"name":{},"text":{"7":{},"11":{},"13":{},"14":{},"18":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.pathparam",{"_index":2756,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.post",{"_index":1816,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.produc",{"_index":1186,"title":{},"name":{},"text":{"7":{},"11":{},"13":{},"14":{},"20":{}},"component":{},"keyword":{}}],["jakarta.ws.rs.queryparam",{"_index":2780,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["januari",{"_index":922,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["jar",{"_index":965,"title":{},"name":{},"text":{"4":{},"10":{},"11":{}},"component":{},"keyword":{}}],["java",{"_index":101,"title":{"4-4":{},"4-9":{},"5-1":{},"11-4":{},"11-9":{}},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["java.math.bigdecim",{"_index":1803,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["java.net.uri",{"_index":2833,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["java.util.arraylist",{"_index":1180,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["java.util.concurrent.completablefutur",{"_index":2262,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["java.util.concurrent.completionstag",{"_index":2263,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["java.util.concurrent.timeunit",{"_index":2834,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["java.util.hashmap",{"_index":1776,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["java.util.list",{"_index":1181,"title":{},"name":{},"text":{"7":{},"8":{},"11":{},"12":{},"14":{}},"component":{},"keyword":{}}],["java.util.logging.logg",{"_index":2284,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["java.util.map",{"_index":1777,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["java.util.set",{"_index":1778,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["javac",{"_index":1333,"title":{},"name":{},"text":{"10":{},"11":{}},"component":{},"keyword":{}}],["javadoc",{"_index":1335,"title":{},"name":{},"text":{"10":{},"11":{}},"component":{},"keyword":{}}],["javafx",{"_index":946,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["jax",{"_index":749,"title":{},"name":{},"text":{"3":{},"12":{},"20":{}},"component":{},"keyword":{}}],["jdk",{"_index":804,"title":{"4-4":{},"11-4":{}},"name":{},"text":{"4":{},"10":{},"11":{}},"component":{},"keyword":{}}],["jetbrain",{"_index":942,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["jitter",{"_index":2170,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["job",{"_index":2855,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["journey",{"_index":818,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["jpa",{"_index":318,"title":{},"name":{},"text":{"2":{},"9":{},"11":{},"12":{}},"component":{},"keyword":{}}],["jre",{"_index":1331,"title":{},"name":{},"text":{"10":{},"11":{}},"component":{},"keyword":{}}],["json",{"_index":319,"title":{"12-11":{},"12-12":{},"12-13":{},"12-14":{},"15-11":{},"19-3":{},"19-8":{},"20-19":{}},"name":{},"text":{"2":{},"3":{},"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"19":{},"20":{}},"component":{},"keyword":{}}],["jsongener",{"_index":1386,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jsonpars",{"_index":1385,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jsonwebtoken",{"_index":2683,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["json’",{"_index":1391,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["jti",{"_index":2637,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["june",{"_index":419,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["junit",{"_index":1063,"title":{},"name":{},"text":{"5":{},"10":{},"11":{}},"component":{},"keyword":{}}],["jupit",{"_index":1064,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["jvm",{"_index":829,"title":{},"name":{},"text":{"4":{},"10":{},"11":{},"14":{},"15":{},"16":{}},"component":{},"keyword":{}}],["jwt",{"_index":119,"title":{"19":{},"19-2":{},"19-3":{},"19-4":{},"19-5":{},"19-14":{},"19-19":{},"19-20":{},"19-22":{},"19-23":{},"19-28":{},"19-37":{},"19-41":{}},"name":{},"text":{"2":{},"3":{},"4":{},"6":{},"11":{},"19":{},"20":{}},"component":{},"keyword":{}}],["jwt.getclaim(\"tenant_id",{"_index":2692,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["jwt.getgroup",{"_index":2691,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["jwt.getnam",{"_index":2690,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["jwt.io",{"_index":2593,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["jwt’",{"_index":2656,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["kafka",{"_index":2456,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["keep",{"_index":1139,"title":{},"name":{},"text":{"6":{},"11":{},"12":{},"14":{},"15":{},"17":{}},"component":{},"keyword":{}}],["key",{"_index":172,"title":{"12-4":{},"12-6":{},"12-8":{},"12-10":{},"12-12":{},"12-14":{},"12-16":{},"14-7":{},"15-4":{},"17-3":{},"19-21":{},"20-3":{},"20-26":{}},"name":{},"text":{"2":{},"3":{},"4":{},"11":{},"12":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["key/valu",{"_index":1712,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["kickstart",{"_index":969,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["kill",{"_index":2028,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["kind",{"_index":92,"title":{},"name":{},"text":{"2":{},"15":{}},"component":{},"keyword":{}}],["kit",{"_index":803,"title":{"4-4":{},"11-4":{}},"name":{},"text":{"4":{},"10":{},"11":{}},"component":{},"keyword":{}}],["km",{"_index":2729,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["knowledg",{"_index":132,"title":{},"name":{},"text":{"2":{},"7":{},"11":{},"12":{},"16":{},"20":{}},"component":{},"keyword":{}}],["known",{"_index":742,"title":{},"name":{},"text":{"3":{},"4":{},"11":{}},"component":{},"keyword":{}}],["kotlin",{"_index":914,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["kubernet",{"_index":651,"title":{"15-21":{}},"name":{},"text":{"3":{},"4":{},"7":{},"11":{},"15":{},"19":{}},"component":{},"keyword":{}}],["lack",{"_index":2237,"title":{},"name":{},"text":{"17":{},"18":{}},"component":{},"keyword":{}}],["laid",{"_index":1575,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["landscap",{"_index":540,"title":{},"name":{},"text":{"3":{},"12":{}},"component":{},"keyword":{}}],["languag",{"_index":96,"title":{},"name":{},"text":{"2":{},"4":{},"11":{},"12":{},"13":{},"18":{}},"component":{},"keyword":{}}],["larg",{"_index":1380,"title":{},"name":{},"text":{"12":{},"13":{},"18":{}},"component":{},"keyword":{}}],["larger",{"_index":1378,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["latenc",{"_index":1114,"title":{},"name":{},"text":{"6":{},"11":{},"14":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["later",{"_index":705,"title":{},"name":{},"text":{"3":{},"4":{},"11":{},"14":{}},"component":{},"keyword":{}}],["latest",{"_index":335,"title":{},"name":{},"text":{"2":{},"4":{},"11":{},"12":{}},"component":{},"keyword":{}}],["launch",{"_index":1657,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["launcher",{"_index":631,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["law",{"_index":84,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["lay",{"_index":709,"title":{},"name":{},"text":{"3":{},"12":{}},"component":{},"keyword":{}}],["layer",{"_index":616,"title":{},"name":{},"text":{"3":{},"9":{},"11":{},"12":{},"19":{},"20":{}},"component":{},"keyword":{}}],["ldap",{"_index":2650,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["lead",{"_index":1137,"title":{},"name":{},"text":{"6":{},"11":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["leader",{"_index":422,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["leak",{"_index":1856,"title":{},"name":{},"text":{"14":{},"20":{}},"component":{},"keyword":{}}],["lean",{"_index":1079,"title":{},"name":{},"text":{"6":{},"11":{}},"component":{},"keyword":{}}],["learn",{"_index":48,"title":{"2-9":{}},"name":{},"text":{"2":{},"3":{},"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"18":{}},"component":{},"keyword":{}}],["learnt",{"_index":1740,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["led",{"_index":488,"title":{},"name":{},"text":{"3":{},"13":{}},"component":{},"keyword":{}}],["left",{"_index":503,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["legaci",{"_index":2512,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["legal",{"_index":41,"title":{"2-1":{}},"name":{},"text":{},"component":{},"keyword":{}}],["less",{"_index":1363,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["let",{"_index":1721,"title":{},"name":{},"text":{"14":{},"20":{}},"component":{},"keyword":{}}],["let’",{"_index":825,"title":{},"name":{},"text":{"4":{},"7":{},"11":{},"12":{},"14":{},"15":{}},"component":{},"keyword":{}}],["level",{"_index":185,"title":{},"name":{},"text":{"2":{},"12":{},"14":{},"16":{},"17":{},"20":{}},"component":{},"keyword":{}}],["leverag",{"_index":461,"title":{},"name":{},"text":{"3":{},"4":{},"6":{},"7":{},"8":{},"10":{},"11":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["li",{"_index":2056,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["liberti",{"_index":630,"title":{"4-14":{},"11-14":{},"14-16":{}},"name":{},"text":{"3":{},"4":{},"7":{},"11":{},"13":{},"14":{},"15":{}},"component":{},"keyword":{}}],["liberty.var.app.context.root>mp",{"_index":1244,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["liberty.var.default.http.port>908094431717&name=4.0.0warutf",{"_index":1043,"title":{},"name":{},"text":{"5":{},"7":{},"11":{}},"component":{},"keyword":{}}],["project.reporting.outputencoding>utf",{"_index":1045,"title":{},"name":{},"text":{"5":{},"7":{},"11":{}},"component":{},"keyword":{}}],["project’",{"_index":426,"title":{},"name":{},"text":{"3":{},"4":{},"5":{},"6":{},"7":{},"11":{}},"component":{},"keyword":{}}],["prometheu",{"_index":2067,"title":{},"name":{},"text":{"16":{},"18":{},"19":{}},"component":{},"keyword":{}}],["promot",{"_index":207,"title":{},"name":{},"text":{"2":{},"3":{},"12":{},"13":{},"20":{}},"component":{},"keyword":{}}],["prompt",{"_index":1005,"title":{},"name":{},"text":{"5":{},"8":{},"11":{},"14":{}},"component":{},"keyword":{}}],["prone",{"_index":1364,"title":{},"name":{},"text":{"12":{},"20":{}},"component":{},"keyword":{}}],["propag",{"_index":608,"title":{"18-6":{},"19-23":{}},"name":{},"text":{"3":{},"6":{},"11":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["proper",{"_index":2388,"title":{},"name":{},"text":{"18":{},"20":{}},"component":{},"keyword":{}}],["properli",{"_index":1925,"title":{},"name":{},"text":{"15":{},"18":{}},"component":{},"keyword":{}}],["properti",{"_index":1042,"title":{"14-8":{},"14-9":{}},"name":{},"text":{"5":{},"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["properties.get(propertynam",{"_index":1787,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["properties.keyset",{"_index":1791,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["properties.put(\"payment.gateway.apikey",{"_index":1782,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["properties.put(\"payment.gateway.endpoint",{"_index":1784,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["propertynam",{"_index":1771,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["proport",{"_index":2239,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["protect",{"_index":1890,"title":{},"name":{},"text":{"14":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["protocol",{"_index":720,"title":{},"name":{},"text":{"3":{},"12":{},"18":{}},"component":{},"keyword":{}}],["provid",{"_index":129,"title":{},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"8":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["providedcompil",{"_index":2097,"title":{},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["proxi",{"_index":2745,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["prudent",{"_index":1098,"title":{},"name":{},"text":{"6":{},"11":{}},"component":{},"keyword":{}}],["public",{"_index":1160,"title":{"19-21":{}},"name":{},"text":{"7":{},"8":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["publish",{"_index":1590,"title":{},"name":{},"text":{"13":{},"14":{}},"component":{},"keyword":{}}],["pull",{"_index":1794,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["purchas",{"_index":267,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["purg",{"_index":2546,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["purpos",{"_index":163,"title":{},"name":{},"text":{"2":{},"9":{},"11":{},"12":{},"13":{},"14":{},"16":{}},"component":{},"keyword":{}}],["put",{"_index":1345,"title":{"12-27":{}},"name":{},"text":{"12":{},"13":{},"20":{}},"component":{},"keyword":{}}],["python",{"_index":2393,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["python3",{"_index":32,"title":{},"name":{},"text":{"1":{}},"component":{},"keyword":{}}],["python’",{"_index":26,"title":{},"name":{},"text":{"1":{}},"component":{},"keyword":{}}],["p’",{"_index":1398,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["q&a",{"_index":329,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["qa",{"_index":1696,"title":{},"name":{},"text":{"14":{},"18":{}},"component":{},"keyword":{}}],["qr1owv2ug2wypbnbqrrarteek9kdo2w8qdcjihnsjflsdv1inqhwxakh4mqakqtm",{"_index":2615,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["qualifi",{"_index":1667,"title":{},"name":{},"text":{"13":{},"14":{}},"component":{},"keyword":{}}],["qualiti",{"_index":1572,"title":{},"name":{},"text":{"12":{},"14":{}},"component":{},"keyword":{}}],["quantiti",{"_index":1154,"title":{},"name":{},"text":{"7":{},"11":{}},"component":{},"keyword":{}}],["quarku",{"_index":632,"title":{"4-15":{},"11-15":{}},"name":{},"text":{"3":{},"4":{},"7":{},"11":{},"13":{},"15":{},"19":{}},"component":{},"keyword":{}}],["quartu",{"_index":960,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["queri",{"_index":741,"title":{"20-13":{}},"name":{},"text":{"3":{},"12":{},"13":{},"14":{},"15":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["queryparam",{"_index":2734,"title":{"20-13":{},"20-14":{}},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["queu",{"_index":2358,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["queue",{"_index":2086,"title":{},"name":{},"text":{"16":{},"17":{}},"component":{},"keyword":{}}],["quick",{"_index":948,"title":{},"name":{},"text":{"4":{},"11":{},"18":{}},"component":{},"keyword":{}}],["quickli",{"_index":545,"title":{},"name":{},"text":{"3":{},"13":{},"16":{},"17":{}},"component":{},"keyword":{}}],["quickstart",{"_index":996,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["random",{"_index":2219,"title":{},"name":{},"text":{"17":{},"18":{}},"component":{},"keyword":{}}],["rang",{"_index":670,"title":{},"name":{},"text":{"3":{},"4":{},"6":{},"11":{},"13":{},"16":{},"18":{}},"component":{},"keyword":{}}],["rapid",{"_index":508,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["rapidli",{"_index":544,"title":{},"name":{},"text":{"3":{},"6":{},"11":{},"18":{}},"component":{},"keyword":{}}],["rate",{"_index":2065,"title":{},"name":{},"text":{"16":{},"18":{}},"component":{},"keyword":{}}],["ratio",{"_index":2243,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["rbac",{"_index":789,"title":{"19-29":{},"19-33":{}},"name":{},"text":{"3":{},"18":{},"19":{}},"component":{},"keyword":{}}],["re",{"_index":2645,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["reach",{"_index":2349,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["react",{"_index":1874,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["reactiv",{"_index":548,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["read",{"_index":384,"title":{"14-9":{}},"name":{},"text":{"2":{},"12":{},"14":{},"19":{},"20":{}},"component":{},"keyword":{}}],["readabl",{"_index":1356,"title":{},"name":{},"text":{"12":{},"13":{},"15":{},"20":{}},"component":{},"keyword":{}}],["reader",{"_index":338,"title":{},"name":{},"text":{"2":{},"12":{}},"component":{},"keyword":{}}],["readi",{"_index":583,"title":{"15-8":{}},"name":{},"text":{"3":{},"12":{},"14":{},"15":{}},"component":{},"keyword":{}}],["readili",{"_index":1678,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["readinessprob",{"_index":2033,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["readm",{"_index":4,"title":{"1":{}},"name":{"1":{}},"text":{},"component":{},"keyword":{}}],["readme.adoc",{"_index":1009,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["readtimeout",{"_index":2853,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["readtimeout(5",{"_index":2843,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["real",{"_index":143,"title":{},"name":{},"text":{"2":{},"3":{},"7":{},"11":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["realist",{"_index":2258,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["reason",{"_index":2340,"title":{},"name":{},"text":{"17":{},"19":{}},"component":{},"keyword":{}}],["reattempt",{"_index":2370,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["receiv",{"_index":235,"title":{},"name":{},"text":{"2":{},"14":{},"15":{},"18":{},"19":{}},"component":{},"keyword":{}}],["recipi",{"_index":2635,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["recogn",{"_index":1599,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["recommend",{"_index":853,"title":{},"name":{},"text":{"4":{},"11":{},"12":{},"13":{},"14":{},"17":{},"20":{}},"component":{},"keyword":{}}],["recompil",{"_index":1687,"title":{},"name":{},"text":{"14":{},"18":{},"20":{}},"component":{},"keyword":{}}],["record",{"_index":2435,"title":{},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["recov",{"_index":765,"title":{},"name":{},"text":{"3":{},"15":{},"17":{}},"component":{},"keyword":{}}],["recover",{"_index":2213,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["recoveri",{"_index":586,"title":{},"name":{},"text":{"3":{},"15":{},"17":{}},"component":{},"keyword":{}}],["red",{"_index":451,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["redeploy",{"_index":1688,"title":{},"name":{},"text":{"14":{},"18":{}},"component":{},"keyword":{}}],["redirect",{"_index":2747,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["reduc",{"_index":523,"title":{},"name":{},"text":{"3":{},"6":{},"11":{},"12":{},"14":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["ref",{"_index":1648,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["refer",{"_index":944,"title":{},"name":{},"text":{"4":{},"7":{},"11":{},"15":{},"17":{},"18":{}},"component":{},"keyword":{}}],["referenc",{"_index":1669,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["reflect",{"_index":252,"title":{},"name":{},"text":{"2":{},"14":{},"15":{}},"component":{},"keyword":{}}],["refresh",{"_index":1861,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["regard",{"_index":70,"title":{},"name":{},"text":{"2":{},"18":{}},"component":{},"keyword":{}}],["region",{"_index":2640,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["regist",{"_index":102,"title":{"14-14":{}},"name":{},"text":{"2":{},"7":{},"11":{},"14":{},"15":{},"16":{},"20":{}},"component":{},"keyword":{}}],["registerprovid",{"_index":2820,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["registerprovider(productserviceresponseexceptionmapper.class",{"_index":2819,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["registerrestcli",{"_index":2733,"title":{"20-8":{}},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["registerrestclient(configkey",{"_index":2759,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["registr",{"_index":215,"title":{},"name":{},"text":{"2":{}},"component":{},"keyword":{}}],["registri",{"_index":2060,"title":{"16-9":{},"16-10":{}},"name":{},"text":{"16":{}},"component":{},"keyword":{}}],["regul",{"_index":2384,"title":{"18-42":{}},"name":{},"text":{"18":{}},"component":{},"keyword":{}}],["regular",{"_index":1750,"title":{},"name":{},"text":{"14":{},"16":{}},"component":{},"keyword":{}}],["regularli",{"_index":1893,"title":{},"name":{},"text":{"14":{},"18":{}},"component":{},"keyword":{}}],["reinvent",{"_index":528,"title":{},"name":{},"text":{"3":{}},"component":{},"keyword":{}}],["reject",{"_index":2348,"title":{},"name":{},"text":{"17":{},"19":{}},"component":{},"keyword":{}}],["relat",{"_index":181,"title":{},"name":{},"text":{"2":{},"4":{},"5":{},"7":{},"9":{},"11":{},"12":{},"14":{},"15":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["relationship",{"_index":302,"title":{"3-11":{}},"name":{},"text":{"2":{},"3":{},"18":{}},"component":{},"keyword":{}}],["releas",{"_index":904,"title":{},"name":{},"text":{"4":{},"11":{},"20":{}},"component":{},"keyword":{}}],["relev",{"_index":764,"title":{},"name":{},"text":{"3":{},"4":{},"6":{},"7":{},"11":{},"12":{},"15":{},"16":{},"18":{}},"component":{},"keyword":{}}],["reli",{"_index":2829,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["reliabl",{"_index":622,"title":{},"name":{},"text":{"3":{},"6":{},"8":{},"11":{},"14":{},"15":{},"16":{},"17":{},"18":{}},"component":{},"keyword":{}}],["reload",{"_index":1876,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["remain",{"_index":1898,"title":{},"name":{},"text":{"14":{},"15":{},"17":{},"18":{}},"component":{},"keyword":{}}],["rememb",{"_index":1076,"title":{},"name":{},"text":{"5":{},"11":{}},"component":{},"keyword":{}}],["remot",{"_index":1709,"title":{},"name":{},"text":{"14":{},"18":{},"20":{}},"component":{},"keyword":{}}],["remov",{"_index":196,"title":{},"name":{},"text":{"2":{},"6":{},"11":{},"12":{},"14":{},"20":{}},"component":{},"keyword":{}}],["render",{"_index":1507,"title":{},"name":{},"text":{"12":{},"13":{},"15":{}},"component":{},"keyword":{}}],["renown",{"_index":870,"title":{},"name":{},"text":{"4":{},"11":{}},"component":{},"keyword":{}}],["reorder",{"_index":1376,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["repackag",{"_index":1104,"title":{},"name":{},"text":{"6":{},"11":{}},"component":{},"keyword":{}}],["repeat",{"_index":1340,"title":{},"name":{},"text":{"10":{},"11":{},"17":{},"18":{},"19":{}},"component":{},"keyword":{}}],["replac",{"_index":1221,"title":{},"name":{},"text":{"7":{},"11":{},"13":{},"14":{},"16":{},"17":{},"20":{}},"component":{},"keyword":{}}],["replic",{"_index":2341,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["repo",{"_index":5,"title":{},"name":{},"text":{"1":{},"2":{}},"component":{},"keyword":{}}],["report",{"_index":1108,"title":{},"name":{},"text":{"6":{},"11":{},"15":{},"16":{}},"component":{},"keyword":{}}],["repositori",{"_index":180,"title":{"12-19":{}},"name":{},"text":{"2":{},"4":{},"8":{},"9":{},"11":{},"12":{},"14":{},"16":{},"20":{}},"component":{},"keyword":{}}],["repres",{"_index":441,"title":{},"name":{},"text":{"3":{},"5":{},"7":{},"9":{},"11":{},"12":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["represent",{"_index":1147,"title":{},"name":{},"text":{"7":{},"10":{},"11":{},"12":{}},"component":{},"keyword":{}}],["request",{"_index":619,"title":{"12-25":{},"12-27":{},"19-22":{},"20-18":{}},"name":{},"text":{"3":{},"6":{},"7":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["requestbodi",{"_index":1650,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["requestscop",{"_index":1434,"title":{},"name":{},"text":{"12":{},"14":{},"17":{}},"component":{},"keyword":{}}],["requestvolumethreshold",{"_index":2191,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["requir",{"_index":82,"title":{"14-3":{}},"name":{},"text":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"9":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["rerout",{"_index":1908,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["rerun",{"_index":1303,"title":{},"name":{},"text":{"8":{},"11":{}},"component":{},"keyword":{}}],["resili",{"_index":125,"title":{},"name":{},"text":{"2":{},"3":{},"6":{},"11":{},"12":{},"14":{},"16":{},"17":{},"18":{},"20":{}},"component":{},"keyword":{}}],["resolv",{"_index":1304,"title":{},"name":{},"text":{"8":{},"11":{},"17":{}},"component":{},"keyword":{}}],["resourc",{"_index":753,"title":{"7-3":{},"8-4":{},"11-26":{},"11-36":{},"12-21":{},"17-44":{},"17-45":{},"17-46":{},"17-51":{}},"name":{},"text":{"3":{},"4":{},"5":{},"6":{},"7":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["respect",{"_index":107,"title":{},"name":{},"text":{"2":{},"15":{},"20":{}},"component":{},"keyword":{}}],["respond",{"_index":1833,"title":{},"name":{},"text":{"14":{},"15":{},"17":{}},"component":{},"keyword":{}}],["respons",{"_index":213,"title":{"15-11":{},"16-12":{},"20-18":{}},"name":{},"text":{"2":{},"3":{},"7":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{}},"component":{},"keyword":{}}],["response.getstatu",{"_index":2825,"title":{},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["response.ok(\"ord",{"_index":2703,"title":{},"name":{},"text":{"19":{}},"component":{},"keyword":{}}],["response.ok(\"{\\\"status\\\":\\\"fail",{"_index":2313,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["response.ok(\"{\\\"status\\\":\\\"success",{"_index":2209,"title":{},"name":{},"text":{"17":{}},"component":{},"keyword":{}}],["response.ok(products).build",{"_index":1481,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["response.ok(result",{"_index":1829,"title":{},"name":{},"text":{"14":{}},"component":{},"keyword":{}}],["response.status(response.status.cr",{"_index":1529,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["response.status(response.status.not_found",{"_index":1557,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["response.status(response.status.ok",{"_index":1554,"title":{},"name":{},"text":{"12":{}},"component":{},"keyword":{}}],["responsebuild",{"_index":2003,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["responsebuilder.build",{"_index":2012,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["responsebuilder.down",{"_index":2011,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["responsebuilder.up",{"_index":2010,"title":{},"name":{},"text":{"15":{}},"component":{},"keyword":{}}],["responsecod",{"_index":1623,"title":{},"name":{},"text":{"13":{}},"component":{},"keyword":{}}],["responseexceptionmapp",{"_index":2736,"title":{"20-21":{}},"name":{},"text":{"20":{}},"component":{},"keyword":{}}],["responseexceptionmapperimportprovidedtestproductserverpom1.0",{"_index":1039,"title":{},"name":{},"text":{"5":{},"7":{},"11":{}},"component":{},"keyword":{}}],["version>1.18.261.11.29.010.0.02.13.0.03.03.1.13.13.3.23.8.24.0.14.0.24.1.15.1.05.1.15.8.26.1${liberty.var.app.context.root} 4.0.0 io.microprofile.tutorial mp-ecomm 1.0-SNAPSHOT war 17 17 ... ... org.projectlombok lombok 1.18.26 provided jakarta.platform jakarta.jakartaee-api 10.0.0 provided org.eclipse.microprofile microprofile 6.1 pom provided org.junit.jupiter junit-jupiter-api 5.8.2 test org.junit.jupiter junit-jupiter-engine 5.8.2 test ... Below is the list of essential dependencies you need to add to your Maven pom.xml for a MicroProfile project: Lombok Dependency - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code. Jakarta EE API Dependency - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications. MicroProfile Dependency - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project. JUnit Jupiter API for Writing Tests - Essential for writing unit tests for your MicroProfile services. JUnit Jupiter Engine for Running Tests - Enables the execution of JUnit tests. These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​. Execute the $ mvn validate command. This checks the pom.xml file for correctness, ensuring that all necessary configuration is present and valid.","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-01","url":"/microprofile-tutorial/6.1/chapter02/chapter02-01.html","titles":[{"text":"Creating a Java Project for MicroProfile Development","hash":"creating-a-java-project-for-microprofile-development","id":1},{"text":"Using Your IDE","hash":"using-your-ide","id":2},{"text":"Using Maven from Command Line (Terminal)","hash":"using-maven-from-command-line-terminal","id":3}]},"6":{"id":6,"text":"Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements. Before diving into MicroProfile modules, it’s essential to have a clear understanding of your application’s requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it’s important to understand which ones best fit your project’s needs. This section aims to help you make informed decisions about which MicroProfile modules to use. If you’re beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently. For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications. Maven org.eclipse.microprofile microprofile 6.1 pom provided Gradle dependencies { compileOnly 'org.eclipse.microprofile:microprofile:6.1' } For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application’s footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time. To prevent potential conflicts or security issues associated with unused dependencies, it’s prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities. The list below is provided to help you select the appropriate modules for your MicroProfile application: MicroProfile Config provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging. Maven org.eclipse.microprofile.config microprofile-config-api 3.1 MicroProfile Health allows you to define health endpoints easily. If you’re deploying your application in a environment where the service needs to report its health status. Maven org.eclipse.microprofile.health microprofile-health-api 4.0.1 MicroProfile Metrics offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application. Maven org.eclipse.microprofile.metrics microprofile-metrics-api 5.1.0 MicroProfile Fault Tolerance helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it. org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api 4.0.2 MicroProfile JWT Authentication provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services. org.eclipse.microprofile.jwt microprofile-jwt-auth-api 2.1 MicroProfile OpenAPI offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs. Maven org.eclipse.microprofile.openapi microprofile-openapi-api 3.1.1 MicroProfile Rest Client simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services. Maven org.eclipse.microprofile.rest.client microprofile-rest-client-api 3.0 MicroProfile Telemetry integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor. Maven io.opentelemetry opentelemetry-bom 1.29.0 pom import io.opentelemetry opentelemetry-api Jakarta EE Core Profile dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. Maven jakarta.platform jakarta.jakartaee-api 10.0.0 provided For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions. When choosing MicroProfile modules, start with the minimal set that meets your application’s core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment.","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-02","url":"/microprofile-tutorial/6.1/chapter02/chapter02-02.html","titles":[{"text":"Choosing Right Modules for Your MicroProfile Application","hash":"choosing-right-modules-for-your-microprofile-application","id":1},{"text":"Use the Entire MicroProfile Dependency","hash":"use-the-entire-microprofile-dependency","id":2},{"text":"Use Individual MicroProfile Specification Dependencies","hash":"use-individual-microprofile-specification-dependencies","id":3}]},"7":{"id":7,"text":"Web Services are very popular nowadays because they allow for building decoupled systems – services can communicate with each other without the knowledge of each other’s implementation details. There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) architecture. A Jakarta RESTful Webservice is a web service that uses the Representational State Transfer (REST) architecture. This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web applications. To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) requests this URL, the server would return a list of products in JSON format. An Entity class represents a specific object, in our case a product. It contains the product’s details id and name, and other properties like price, and quantity. To implement an entity class first, you need to create a Product class, as below: // Product.java package io.microprofile.tutorial.store.product.entity; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor @NoArgsConstructor public class Product { private Long id; private String name; private String description; private Double price; } Explanation: The Product class is a Plain Old Java Object (POJO). It has an id, name, description and price property. The id property is of type Long, The name and description properties are of type String. The price property is of type Double. @Data annotation will generate constructors, getters, and setters for all fields. By doing this, you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of Object type as well. Jackson cannot work with primitive types because they cannot be null. @AllArgsConstructor generates a constructor with one argument for each field in the class. This is useful for instantiating objects with all their fields initialized. @NoArgsConstructor generates a default constructor for the class. A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving (CRUD) operations on the resources. Let us now create a ProductResource class with a getProducts() method to return a list of Product objects. // ProductResource.java package io.microprofile.tutorial.store.product.resource; import java.util.ArrayList; import java.util.List; import io.microprofile.tutorial.store.product.entity.Product; import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path(\"/products\") @ApplicationScoped public class ProductResource { private List products = new ArrayList<>(); public ProductResource() { // Initialize the list with some sample products products.add(new Product(Long.valueOf(1L), \"iPhone\", \"Apple iPhone 15\", Double.valueOf(999.99))); products.add(new Product(Long.valueOf(2L), \"MacBook\", \"Apple MacBook Air\", Double.valueOf(1299.99))); } @GET @Produces(MediaType.APPLICATION_JSON) public List getProducts() { return products; } } Explanation: The ProductResource is annotated with @ApplicationScoped. This will ensure that only one instance of this class available when the application is running. The ProductResource class has a getProducts() method, which returns a list of products. This method is annotated with the @GET annotation, which maps this method to the GET HTTP method. The @Produces annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a GET request to the /api/products endpoint. RESTful web services can produce and consume many different media types, including JSON, XML, and HTML. Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with @Produces(MediaType.APPLICATION_JSON) it can produce JSON. Create a class named ProductRestApplication as per the code below: // ProductRestApplication.java package io.microprofile.tutorial.store.product; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath(\"/api\") public class ProductRestApplication extends Application{ } Explanation: The annotation @ApplicationPath(\"/api\") specifies that any RESTful resources registered within this application will be accessed under the base path /api. For example, if you have a resource class named ProductResource mapped to the path /products, it would be accessible at /api/products. You may build the application using the following commands from your project’s root directory: $ mvn compile The above command will compile your project’s source code. $ mvn test The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed. $mvn package The above command will create a deployment package. This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations: Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project. Packaging: Decide on a packaging format (e.g., WAR file, Docker image). Configuration: Review and adjust any runtime configuration necessary for your service. Deployment Tools: Leverage runtime-specific tools or commands for deployment. You can then deploy this application on a MicroProfile compatible server and access the web service at http://localhost://api/products. Replace with the port number on which the web server or application server is listening. The is a placeholder for the context root of the web application. The context root is part of the URL path that identifies the base path for the application on the web server. Below are the steps for popular options. Specific steps will depend on your chosen runtime. Open Liberty Package your application as a WAR file using Maven or Gradle by adding the packaging tag in pom.xml. io.microprofile.tutorial mp-ecomm-store 1.0-SNAPSHOT war Add a server configuration file at the location /main/liberty/config/server.xml with the content as below: restfulWS-3.1 jsonb-3.0 Add the Open Liberty configuration in the pom.xml as below: UTF-8 UTF-8 17 17 9080 9443 mp-ecomm-store Add the Open Liberty build plugin in the pom.xml as below: ${project.artifactId} org.apache.maven.plugins maven-war-plugin 3.3.2 io.openliberty.tools liberty-maven-plugin 3.8.2 productServer org.apache.maven.plugins maven-failsafe-plugin 3.0.0 ${liberty.var.default.http.port} ${liberty.var.app.context.root} Refer to your runtime’s documentation for instructions on running your MicroProfile application. For example, Consult the Open Liberty documentation for detailed instructions: MicroProfile - Open Liberty Docs Finally, use the following command from the command line or terminal to run the application on Liberty server. $ mvn liberty:run You can also run the following command to start the liberty server in development mode. $ mvn liberty:dev Assuming your server is running on http://localhost:9080/, you can access your service at: http://localhost:9080/mp-ecomm-store/api/products. To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. The response should be: [{\"description\":\"Apple iPhone 15\",\"id\":1,\"name\":\"iPhone\",\"price\":999.99},{\"description\":\"Apple MacBook Air\",\"id\":2,\"name\":\"MacBook\",\"price\":1299.99}] This uses an in-memory list; In a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter. Quarkus Build your application as a native executable or Docker image. Run the generated executable or deploy the Docker image to a container platform. Refer to the Quarkus documentation for deployment guides: Creating your first application - Quarkus Payara Micro Package your application as a WAR file. Deploy the WAR to a Payara Micro server instance. See the Payara Micro documentation for specific instructions: Getting Started with Payara Micro WildFly Package your application as a WAR file. Deploy the WAR to a WildFly server instance. Refer to the WildFly documentation for deployment details: WildFly Developer Guide Helidon Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging). Build your application using Gradle. Follow the relevant Helidon documentation for deployment steps: Helidon - Getting Started TomEE Package your application as a WAR file. Deploy the WAR file to the TomEE server instance. Refer to the TomEE documentation for instructions: Serverless TomEE MicroProfile Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability. Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP.","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-03","url":"/microprofile-tutorial/6.1/chapter02/chapter02-03.html","titles":[{"text":"Developing a RESTful Web Service","hash":"developing-a-restful-web-service","id":1},{"text":"Creating an Entity class","hash":"creating-an-entity-class","id":2},{"text":"Creating a Resource class","hash":"creating-a-resource-class","id":3},{"text":"Creating an Applicaiton class","hash":"creating-an-applicaiton-class","id":4},{"text":"Building Your Application","hash":"building-your-application","id":5},{"text":"Deploying your microservices","hash":"deploying-your-microservices","id":6},{"text":"General Considerations:","hash":"general-considerations","id":7},{"text":"Deployment Options","hash":"deployment-options","id":8},{"text":"Running Your Application","hash":"running-your-application","id":9},{"text":"Additional Considerations:","hash":"additional-considerations","id":10}]},"8":{"id":8,"text":"Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. To create tests for your microservice, start by creating a class called ProductResourceTest, which contains unit tests for the ProductResource class as below: // ProductResourceTest.java package io.microprofile.tutorial.store.product.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.microprofile.tutorial.store.product.entity.Product; public class ProductResourceTest { private ProductResource productResource; @BeforeEach void setUp() { productResource = new ProductResource(); } @AfterEach void tearDown() { productResource = null; } @Test void testGetProducts() { List products = productResource.getProducts(); assertNotNull(products); assertEquals(2, products.size()); } } Explanation: Below are the assertions to test the getProducts() method of ProductService: * The assertNotNull(products); assertion checks that products are not null. It ensures the method returns a list, even if it’s empty. * The assertEquals(2, products.size()); assertion verifies that the list contains two products. To run the unit tests defined in ProductResourceTest, follow these steps: Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations. Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests: $ mvn test This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results. Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions: Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly. Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected. By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment. Now that you have a basic MicroProfile service, consider exploring further: Adding configuration with MicroProfile Config Implementing health checks using MicroProfile Health Enhancing your service with MicroProfile Fault Tolerance MicroProfile Official Website: https://microprofile.io/ MicroProfile GitHub Repository: https://github.com/eclipse/microprofile MicroProfile Documentation and Guides: MicroProfile Official documentation After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture.","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-04","url":"/microprofile-tutorial/6.1/chapter02/chapter02-04.html","titles":[{"text":"Testing Your Microservice","hash":"testing-your-microservice","id":1},{"text":"Running Unit Tests with Maven","hash":"running-unit-tests-with-maven","id":2},{"text":"Next Steps","hash":"next-steps","id":3},{"text":"Resources","hash":"resources","id":4}]},"9":{"id":9,"text":"The Table below provides an overview of the package structure and their purposes within a typical Java-based microservices architecture. Package Description dto Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data. entity Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA. repository Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository). service Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService). resource REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource). common This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes. client For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services. config Configuration classes for MicroProfile Config. exception Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException). util Helper and utility classes. Base Package: io.microprofile.tutorial.store io.microprofile.tutorial.store ├── catalog │ ├── resource │ ├── config │ ├── exception │ ├── entity │ ├── repository │ ├── service │ └── util ├── cart │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── client │ ├── exception │ └── util ├── user │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── inventory │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── order │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── payment │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util └── shipment ├── resource ├── entity ├── service ├── repository ├── exception └── util ----","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-05","url":"/microprofile-tutorial/6.1/chapter02/chapter02-05.html","titles":[{"text":"Package Structure","hash":"package-structure","id":1}]},"10":{"id":10,"text":"Java Development Kit (JDK) A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development. Integrated Development Environment (IDE) A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code. RESTful Service A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet. Runtime Environment The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables. JUnit A unit testing framework for Java, used to write and run repeatable tests. Containerization A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment. Cloud Deployment Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities.","title":"","component":"microprofile-tutorial","version":"6.1","name":"chapter02-06","url":"/microprofile-tutorial/6.1/chapter02/chapter02-06.html","titles":[{"text":"Glossary","hash":"glossary","id":1}]},"11":{"id":11,"text":"In this chapter, you’ll embark on your MicroProfile journey! We will guide you through creating your first microservice, equipping you with the essential understanding to leverage this robust framework for building modern, cloud-native applications. This journey begins with setting up your development environment, then diving into creating a microservice. Development Environment Setup Configuring Build Tools Initializing a New MicroProfile Project Choosing Right Modules for your Application Building a RESTful service Deployment Testing your microservices Exploring Further with MicroProfile Let’s begin by preparing your workspace for MicroProfile development: MicroProfile, a Java framework, runs on the Java Virtual Machine (JVM), making the Java Development Kit (JDK) an essential component of your development environment. To install JDK, follow the steps below: Download: Visit the official OpenJDK website and download the JDK version compatible with your operating system. Install: Follow the installation instructions provided on this official OpenJDK Installation guide. Verify: After Installation, run the following command in your command line or terminal to verify the Installation: java -version You should an output similar to the one below: openjdk 17.0.10 2024-01-16 LTS OpenJDK Runtime Environment Microsoft-8902769 (build 17.0.10+7-LTS) OpenJDK 64-Bit Server VM Microsoft-8902769 (build 17.0.10+7-LTS, mixed mode, sharing) This confirms that JDK 17 has been successfully installed on your system. For most MicroProfile implementations, JDK 11 or later is recommended. In this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK providers such as Oracle JDK, Amazon Corretto, Azul Zulu, OpenJ9 also offer compatible JDK distributions. Build tools like Apache Maven or Gradle are commonly used for managing project dependencies and building Java applications. You can install the one that best fits your project needs. Here’s a brief overview to help you decide: Apache Maven: Known for its convention-over-configuration approach, Maven is a popular choice due to its simple project setup and extensive plugin repository. Gradle: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It’s a great choice for complex projects requiring customization. If your existing project’s build uses Maven wrapper (mvnw) or Gradle wrapper (gradlew), you don’t have to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. To install Maven follow the steps below: Visit the Installing Apache Maven web page to download the latest version. Follow the installation instructions provided on the site. Verify the Maven installation by running this command in your terminal or command line. mvn -v You should see output similar to: Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: /usr/local/sdkman/candidates/maven/current Java version: 17.0.10, vendor: Microsoft, runtime: /usr/lib/jvm/msopenjdk-current Default locale: en_US, platform encoding: UTF-8 OS name: \"linux\", version: \"6.2.0-1019-azure\", arch: \"amd64\", family: \"unix\" After Maven is installed, you can configure the pom.xml file in your project to include the MicroProfile dependencies. To install Gradle follow the step below: Visit the Gradle | Installation web page to download the latest version. Follow the installation instructions provided on the site. Verify the installation by running this command in your terminal or command line. gradle -version You should see output similar to: Welcome to Gradle 8.6! Here are the highlights of this release: - Configurable encryption key for configuration cache - Build init improvements - Build authoring improvements For more details see https://docs.gradle.org/8.6/release-notes.html ------------------------------------------------------------ Gradle 8.6 ------------------------------------------------------------ Build time: 2024-02-02 16:47:16 UTC Revision: d55c486870a0dc6f6278f53d21381396d0741c6e Kotlin: 1.9.20 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 17.0.10 (Microsoft 17.0.10+7-LTS) OS: Linux 6.2.0-1019-azure amd64 After Gradle is installed, you can configure the build.gradle file in your project to include the MicroProfile dependencies. Whether you opt for Maven’s stability and convention or Gradle’s flexibility and performance, understanding how to configure and use your chosen build tool is important for MicroProfile development. Integrated Development Environments (IDEs) enhance developer productivity by providing a rich set of features and extensions such as project boostraping, dependency management, intelligent code completion, configuration assistance, test runners, build, hot deployment and debugging tools. For MicroProfile development, the choice of IDE can significantly affect your development speed and efficiency. Below is a list of popular IDEs and their key features related to Java and MicroProfile development: Overview: Eclipse for Enterprise Java and Web Developers is a widely used IDE for Java development, offering extensive support for Java EE, Jakarta EE, and MicroProfile, among other technologies. Getting Started: The official Eclipse documentation containing instructions about creating Java projects - Creating your first Java Project Overview: IntelliJ IDEA by JetBrains supports a wide range of programming languages and frameworks, including Java, Kotlin, and frameworks like Spring, Jakarta EE, and MicroProfile. Getting Started: Refer to this IntelliJ IDEA guide on Creating a Java Project Using IntelliJ IDEA 2024.1. Overview: NetBeans is an open-source IDE that supports Java development, including Java SE, Java EE, JavaFX, and more. Getting Started: Check out this NetBeans Java Quick Start Tutorial for a tutorial on creating a Java application. Overview: Visual Studio Code is a lightweight, powerful source code editor that supports Java development through extensions. Getting Started: To start with Java in VS Code, follow this Getting Started with Java in VS Code documentation. Selecting an IDE should be based on personal preference, as the best choice varies depending on individual needs, familiarity, and the specific features that enhance your productivity. Each IDE offers unique advantages for MicroProfile development. MicroProfile applications need a runtime that supports MicroProfile specifications or a MicroProfile-compatible server to run your applications. Below are some popular options, each with unique features tailored to different needs: Open Liberty is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease. Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance. Downloading Open Liberty page provides access to its latest releases and documentation to help you set up your environment. Quarkus is known for its container-first approach, offering fast startup times and low memory footprint. It aims to optimize Java for Kubernetes and cloud environments This Getting Started with Quarkus page will guide you through creating your first Quartus project and exploring its cloud-native capabilities. Payara Micro is a lightweight middleware platform suited for containerized Jakarta EE and MicroProfile applications. The Payara Platform Community Edition enables easy packaging of applications into a single, runnable JAR file, simplifying deployment and scaling in cloud environments. This site about Payara Platform Community Edition offers downloads and documentation to get started. WildFly is a flexible, lightweight, managed application runtime that offers full Jakarta EE and MicroProfile support. WildFly is designed for scalability and flexibility in both traditional and cloud-native environments. WildFly Downloads page offers the latest versions and documentation to get you started. Developed by Oracle, Helidon MP implements MicroProfile specifications. It provides a familiar programming model for Jakarta EE developers and enables efficient microservice development. Helidon Documentation provides comprehensive resources to help developers get started with the framework, understand its core concepts, and develop microservices efficiently. Apache TomEE integrates several Apache projects with Apache Tomcat to provide a Jakarta EE environment. It offers support for MicroProfile, allowing developers to build and deploy microservices using the well-known Jakarta EE technologies with additional MicroProfile capabilities. TomEE Downloads and TomEE MicroProfile Documentation page provide the necessary resources to get started with TomEE for MicroProfile development. To kickstart your MicroProfile project, use the MicroProfile Starter to generate a sample project with your chosen server and specifications. This tool provides a customizable project structure and generates necessary boilerplate code and configuration. Visit the MicroProfile Starter page - the official website for generating the MicroProfile project templates. Provide a groupId for your project, it’s an identifier for your project and should be unique to avoid conflicts with other libraries or projects. Its recommended convention is to start your groupId with the reverse domain name of your organization (for example, io.microprofile). Enter the 'artifactID', which is the name of your project (e.g., 'mp-ecomm-store'). Select the Java SE version your project will use. Select the MicroProfile version you want to use. Ideally, you should choose the latest version for the most up-to-date features but also consider the runtime’s support. Select the specifications you want to include in your project. These could be Config, Fault Tolerance, JWT Auth, Metrics, Health, Open API, Open Tracing, Rest Client. Choose what is relevant to your application. Click the Download button. Unzip the generated project and import it into your IDE. This completes the development environment setup. Now we are all set to begin development using MicroProfile. At the time of writing this tutorial, the latest MicroProfile released version was 6.1. The MicroProfile Starter does not currently support this version. Hence, we will not be using MicroProfile Starter to generate the project structure. == Creating a Java Project for MicroProfile Development Most modern IDEs have built-in support for creating Java and Maven projects. Depending on your chosen IDE, look for options like \"New Project\", or \"New Maven Project\" to get started. These options typically guide you through the setup process, including specifying the project’s groupId, artifactId, and dependencies. For developers who prefer using the command line or for those setting up projects in environments without an IDE, Maven can generate the base structure of a Java project through its archetype mechanism. To create a project using Maven, open your terminal or command line and run the following command: mvn archetype:generate -DgroupId=io.microprofile.tutorial -DartifactId=store -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false The above command creates a new Maven project based on the maven-archetype-quickstart archetype, setting the groupId as io.microprofile.tutorial and the artifactID as store. Explanation: mvn archetype:generate goal in this command initializes the project creation process and instructs Maven to generate a new project based on an archetype template. -DgroupId=io.microprofile.tutorial Specifies the groupId for the project. The groupId is a unique identifier for your project and is usually based on the domain name of your organization in reverse. In this case, io.microprofile.tutorial represent a project related to MicroProfile tutorial. -DartifactId=store: Sets the artifactId for the project. The artifactId is the name of the project and is used as the base name for the project’s artifacts (e.g., WAR files). Here, store is used as the project name to indicate this project is related an e-commerce store application. -DarchetypeArtifactId=maven-archetype-quickstart: Indicates which archetype to use for generating the project. The maven-archetype-quickstart is a basic template for a Java application, providing a simple project structure that includes a sample application (App.java) and a unit test (AppTest.java). -DinteractiveMode=false: This option disables interactive mode, meaning Maven will not prompt you for input during the project generation process. All necessary information is provided via the command-line options, allowing the command to execute without further user interaction. Next, using your file explorer or command line, create the following folder structure: . ├── pom.xml ├── README.adoc └── src └── main │ └── java │ └── io │ └── microprofile │ └── tutorial │ └── store │ └── product │ │ ├── entity │ │ │ └── Product.java │ │ └── resource │ │ └── ProductResource.java │ └── ProductRestApplication.java └── test └── java └── io └── microprofile └── tutorial └── store └── product └── ProductServiceTest.java The standard location for your Java source code is this folder: src/main/java And, the standard location for your test code is this folder: src/test/java You can delete App.java and AppTest.java files from the poject as these are not required in MicroProfile development. The heart of your Maven project is pom.xml (Project Object Model) file. It defines project metadata, dependencies, build configurations and plugins. The content for the pom.xml file should look as below, ensure MicroProfile depencency is added: 4.0.0 io.microprofile.tutorial mp-ecomm 1.0-SNAPSHOT war 17 17 ... ... org.projectlombok lombok 1.18.26 provided jakarta.platform jakarta.jakartaee-api 10.0.0 provided org.eclipse.microprofile microprofile 6.1 pom provided org.junit.jupiter junit-jupiter-api 5.8.2 test org.junit.jupiter junit-jupiter-engine 5.8.2 test ... Below is the list of essential dependencies you need to add to your Maven pom.xml for a MicroProfile project: Lombok Dependency - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code. Jakarta EE API Dependency - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications. MicroProfile Dependency - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project. JUnit Jupiter API for Writing Tests - Essential for writing unit tests for your MicroProfile services. JUnit Jupiter Engine for Running Tests - Enables the execution of JUnit tests. These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​. Execute the $ mvn validate command. This checks the pom.xml file for correctness, ensuring that all necessary configuration is present and valid. == Choosing Right Modules for Your MicroProfile Application Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements. Before diving into MicroProfile modules, it’s essential to have a clear understanding of your application’s requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it’s important to understand which ones best fit your project’s needs. This section aims to help you make informed decisions about which MicroProfile modules to use. If you’re beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently. For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications. Maven org.eclipse.microprofile microprofile 6.1 pom provided Gradle dependencies { compileOnly 'org.eclipse.microprofile:microprofile:6.1' } For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application’s footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time. To prevent potential conflicts or security issues associated with unused dependencies, it’s prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities. The list below is provided to help you select the appropriate modules for your MicroProfile application: MicroProfile Config provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging. Maven org.eclipse.microprofile.config microprofile-config-api 3.1 MicroProfile Health allows you to define health endpoints easily. If you’re deploying your application in a environment where the service needs to report its health status. Maven org.eclipse.microprofile.health microprofile-health-api 4.0.1 MicroProfile Metrics offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application. Maven org.eclipse.microprofile.metrics microprofile-metrics-api 5.1.0 MicroProfile Fault Tolerance helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it. org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api 4.0.2 MicroProfile JWT Authentication provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services. org.eclipse.microprofile.jwt microprofile-jwt-auth-api 2.1 MicroProfile OpenAPI offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs. Maven org.eclipse.microprofile.openapi microprofile-openapi-api 3.1.1 MicroProfile Rest Client simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services. Maven org.eclipse.microprofile.rest.client microprofile-rest-client-api 3.0 MicroProfile Telemetry integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor. Maven io.opentelemetry opentelemetry-bom 1.29.0 pom import io.opentelemetry opentelemetry-api Jakarta EE Core Profile dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. Maven jakarta.platform jakarta.jakartaee-api 10.0.0 provided For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions. When choosing MicroProfile modules, start with the minimal set that meets your application’s core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment. == Developing a RESTful Web Service Web Services are very popular nowadays because they allow for building decoupled systems – services can communicate with each other without the knowledge of each other’s implementation details. There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) architecture. A Jakarta RESTful Webservice is a web service that uses the Representational State Transfer (REST) architecture. This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web applications. To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) requests this URL, the server would return a list of products in JSON format. An Entity class represents a specific object, in our case a product. It contains the product’s details id and name, and other properties like price, and quantity. To implement an entity class first, you need to create a Product class, as below: // Product.java package io.microprofile.tutorial.store.product.entity; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor @NoArgsConstructor public class Product { private Long id; private String name; private String description; private Double price; } Explanation: The Product class is a Plain Old Java Object (POJO). It has an id, name, description and price property. The id property is of type Long, The name and description properties are of type String. The price property is of type Double. @Data annotation will generate constructors, getters, and setters for all fields. By doing this, you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of Object type as well. Jackson cannot work with primitive types because they cannot be null. @AllArgsConstructor generates a constructor with one argument for each field in the class. This is useful for instantiating objects with all their fields initialized. @NoArgsConstructor generates a default constructor for the class. A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving (CRUD) operations on the resources. Let us now create a ProductResource class with a getProducts() method to return a list of Product objects. // ProductResource.java package io.microprofile.tutorial.store.product.resource; import java.util.ArrayList; import java.util.List; import io.microprofile.tutorial.store.product.entity.Product; import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path(\"/products\") @ApplicationScoped public class ProductResource { private List products = new ArrayList<>(); public ProductResource() { // Initialize the list with some sample products products.add(new Product(Long.valueOf(1L), \"iPhone\", \"Apple iPhone 15\", Double.valueOf(999.99))); products.add(new Product(Long.valueOf(2L), \"MacBook\", \"Apple MacBook Air\", Double.valueOf(1299.99))); } @GET @Produces(MediaType.APPLICATION_JSON) public List getProducts() { return products; } } Explanation: The ProductResource is annotated with @ApplicationScoped. This will ensure that only one instance of this class available when the application is running. The ProductResource class has a getProducts() method, which returns a list of products. This method is annotated with the @GET annotation, which maps this method to the GET HTTP method. The @Produces annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a GET request to the /api/products endpoint. RESTful web services can produce and consume many different media types, including JSON, XML, and HTML. Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with @Produces(MediaType.APPLICATION_JSON) it can produce JSON. Create a class named ProductRestApplication as per the code below: // ProductRestApplication.java package io.microprofile.tutorial.store.product; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath(\"/api\") public class ProductRestApplication extends Application{ } Explanation: The annotation @ApplicationPath(\"/api\") specifies that any RESTful resources registered within this application will be accessed under the base path /api. For example, if you have a resource class named ProductResource mapped to the path /products, it would be accessible at /api/products. You may build the application using the following commands from your project’s root directory: $ mvn compile The above command will compile your project’s source code. $ mvn test The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed. $mvn package The above command will create a deployment package. This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations: Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project. Packaging: Decide on a packaging format (e.g., WAR file, Docker image). Configuration: Review and adjust any runtime configuration necessary for your service. Deployment Tools: Leverage runtime-specific tools or commands for deployment. You can then deploy this application on a MicroProfile compatible server and access the web service at http://localhost://api/products. Replace with the port number on which the web server or application server is listening. The is a placeholder for the context root of the web application. The context root is part of the URL path that identifies the base path for the application on the web server. Below are the steps for popular options. Specific steps will depend on your chosen runtime. Open Liberty Package your application as a WAR file using Maven or Gradle by adding the packaging tag in pom.xml. io.microprofile.tutorial mp-ecomm-store 1.0-SNAPSHOT war Add a server configuration file at the location /main/liberty/config/server.xml with the content as below: restfulWS-3.1 jsonb-3.0 Add the Open Liberty configuration in the pom.xml as below: UTF-8 UTF-8 17 17 9080 9443 mp-ecomm-store Add the Open Liberty build plugin in the pom.xml as below: ${project.artifactId} org.apache.maven.plugins maven-war-plugin 3.3.2 io.openliberty.tools liberty-maven-plugin 3.8.2 productServer org.apache.maven.plugins maven-failsafe-plugin 3.0.0 ${liberty.var.default.http.port} ${liberty.var.app.context.root} Refer to your runtime’s documentation for instructions on running your MicroProfile application. For example, Consult the Open Liberty documentation for detailed instructions: MicroProfile - Open Liberty Docs Finally, use the following command from the command line or terminal to run the application on Liberty server. $ mvn liberty:run You can also run the following command to start the liberty server in development mode. $ mvn liberty:dev Assuming your server is running on http://localhost:9080/, you can access your service at: http://localhost:9080/mp-ecomm-store/api/products. To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. The response should be: [{\"description\":\"Apple iPhone 15\",\"id\":1,\"name\":\"iPhone\",\"price\":999.99},{\"description\":\"Apple MacBook Air\",\"id\":2,\"name\":\"MacBook\",\"price\":1299.99}] This uses an in-memory list; In a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter. Quarkus Build your application as a native executable or Docker image. Run the generated executable or deploy the Docker image to a container platform. Refer to the Quarkus documentation for deployment guides: Creating your first application - Quarkus Payara Micro Package your application as a WAR file. Deploy the WAR to a Payara Micro server instance. See the Payara Micro documentation for specific instructions: Getting Started with Payara Micro WildFly Package your application as a WAR file. Deploy the WAR to a WildFly server instance. Refer to the WildFly documentation for deployment details: WildFly Developer Guide Helidon Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging). Build your application using Gradle. Follow the relevant Helidon documentation for deployment steps: Helidon - Getting Started TomEE Package your application as a WAR file. Deploy the WAR file to the TomEE server instance. Refer to the TomEE documentation for instructions: Serverless TomEE MicroProfile Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability. Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP. == Testing Your Microservice Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. To create tests for your microservice, start by creating a class called ProductResourceTest, which contains unit tests for the ProductResource class as below: // ProductResourceTest.java package io.microprofile.tutorial.store.product.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.microprofile.tutorial.store.product.entity.Product; public class ProductResourceTest { private ProductResource productResource; @BeforeEach void setUp() { productResource = new ProductResource(); } @AfterEach void tearDown() { productResource = null; } @Test void testGetProducts() { List products = productResource.getProducts(); assertNotNull(products); assertEquals(2, products.size()); } } Explanation: Below are the assertions to test the getProducts() method of ProductService: * The assertNotNull(products); assertion checks that products are not null. It ensures the method returns a list, even if it’s empty. * The assertEquals(2, products.size()); assertion verifies that the list contains two products. To run the unit tests defined in ProductResourceTest, follow these steps: Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations. Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests: $ mvn test This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results. Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions: Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly. Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected. By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment. Now that you have a basic MicroProfile service, consider exploring further: Adding configuration with MicroProfile Config Implementing health checks using MicroProfile Health Enhancing your service with MicroProfile Fault Tolerance MicroProfile Official Website: https://microprofile.io/ MicroProfile GitHub Repository: https://github.com/eclipse/microprofile MicroProfile Documentation and Guides: MicroProfile Official documentation After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture. == Package Structure The Table below provides an overview of the package structure and their purposes within a typical Java-based microservices architecture. Package Description dto Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data. entity Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA. repository Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository). service Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService). resource REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource). common This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes. client For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services. config Configuration classes for MicroProfile Config. exception Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException). util Helper and utility classes. Base Package: io.microprofile.tutorial.store io.microprofile.tutorial.store ├── catalog │ ├── resource │ ├── config │ ├── exception │ ├── entity │ ├── repository │ ├── service │ └── util ├── cart │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── client │ ├── exception │ └── util ├── user │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── inventory │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── order │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util ├── payment │ ├── resource │ ├── entity │ ├── service │ ├── repository │ ├── exception │ └── util └── shipment ├── resource ├── entity ├── service ├── repository ├── exception └── util ---- [glossary] == Glossary Java Development Kit (JDK) A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development. Integrated Development Environment (IDE) A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code. RESTful Service A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet. Runtime Environment The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables. JUnit A unit testing framework for Java, used to write and run repeatable tests. Containerization A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment. Cloud Deployment Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities.","title":"Getting Started with MicroProfile","component":"microprofile-tutorial","version":"6.1","name":"chapter02","url":"/microprofile-tutorial/6.1/chapter02/chapter02.html","titles":[{"text":"Introduction","hash":"introduction","id":1},{"text":"Topics Covered","hash":"topics-covered","id":2},{"text":"Development Environment Setup","hash":"development-environment-setup","id":3},{"text":"Java Development Kit (JDK)","hash":"java-development-kit-jdk","id":4},{"text":"Build Tools (Maven or Gradle)","hash":"build-tools-maven-or-gradle","id":5},{"text":"Installing Apache Maven","hash":"installing-apache-maven","id":6},{"text":"Gradle","hash":"gradle","id":7},{"text":"Integrated Development Environments","hash":"integrated-development-environments","id":8},{"text":"Eclipse for Enterprise Java and Web Developers","hash":"eclipse-for-enterprise-java-and-web-developers","id":9},{"text":"IntelliJ IDEA","hash":"intellij-idea","id":10},{"text":"Apache NetBeans","hash":"apache-netbeans","id":11},{"text":"Visual Studio Code","hash":"visual-studio-code","id":12},{"text":"Setting up MicroProfile Runtime","hash":"setting-up-microprofile-runtime","id":13},{"text":"Open Liberty","hash":"open-liberty","id":14},{"text":"Quarkus","hash":"quarkus","id":15},{"text":"Payara Micro","hash":"payara-micro","id":16},{"text":"WildFly","hash":"wildfly","id":17},{"text":"Helidon","hash":"helidon","id":18},{"text":"Apache TomEE","hash":"apache-tomee","id":19},{"text":"MicroProfile Starter","hash":"microprofile-starter","id":20},{"text":"Using Your IDE","hash":"using-your-ide","id":21},{"text":"Using Maven from Command Line (Terminal)","hash":"using-maven-from-command-line-terminal","id":22},{"text":"Use the Entire MicroProfile Dependency","hash":"use-the-entire-microprofile-dependency","id":23},{"text":"Use Individual MicroProfile Specification Dependencies","hash":"use-individual-microprofile-specification-dependencies","id":24},{"text":"Creating an Entity class","hash":"creating-an-entity-class","id":25},{"text":"Creating a Resource class","hash":"creating-a-resource-class","id":26},{"text":"Creating an Applicaiton class","hash":"creating-an-applicaiton-class","id":27},{"text":"Building Your Application","hash":"building-your-application","id":28},{"text":"Deploying your microservices","hash":"deploying-your-microservices","id":29},{"text":"General Considerations:","hash":"general-considerations","id":30},{"text":"Deployment Options","hash":"deployment-options","id":31},{"text":"Running Your Application","hash":"running-your-application","id":32},{"text":"Additional Considerations:","hash":"additional-considerations","id":33},{"text":"Running Unit Tests with Maven","hash":"running-unit-tests-with-maven","id":34},{"text":"Next Steps","hash":"next-steps","id":35},{"text":"Resources","hash":"resources","id":36}]},"12":{"id":12,"text":"This chapter delves into the Jakarta EE 10 Core Profile, a specification designed specifically for microservices and cloud-native apps. Jakarta EE is a comprehensive framework within the Java ecosystem for crafting enterprise-grade applications. Complementing this, MicroProfile addresses the intricacies of microservices development, such as configuration management, fault tolerance, health checks, and monitoring. The foundation of MicroProfile is built on the strong and established standards of Jakarta EE, which ensures smooth integration of these modern APIs with the enterprise Java landscape. In this chapter, through practical examples, we will explore the critical features of the Jakarta EE 10 Core Profile that are most relevant to microservices development, including Contexts and Dependency Injection (CDI), Jakarta RESTful Web Services (Jakarta REST, formerly JAX-RS), JSON Binding and JSON Processing. By the end of this chapter, you will gain a comprehensive understanding of Jakarta EE 10 Core Profile as a foundational platform for developing microservices with MicroProfile. You will be able to appreciate the pivotal role of Jakarta EE in the MicroProfile ecosystem and how its core functionalities develop scalable, resilient, and portable cloud-native applications. Understanding the Jakarta EE 10 Core Profile Key Specifications in Core Profile Managing Component Dependencies Handling HTTP Methods and Resources Best Practices for Building Robust and Scalable Applications The Jakarta EE 10 Core Profile is a streamlined subset of the full Jakarta EE platform explicitly designed for building lightweight microservices and cloud-native applications. It provides a standardized foundation for smaller runtime environments, comprising of a curated selection of Jakarta EE specifications: Jakarta Annotations: Enables developers to decorate their code with metadata to influence system configuration and behavior, making the code concise, readable, and maintainable. Jakarta Contexts and Dependency Injection Lite: Facilitates the management of lifecycle contexts of stateful components and the injection of dependencies. Jakarta Interceptors: Offers a means to intercept business method invocations and lifecycle events, ideal for implementing cross-cutting concerns such as logging. Jakarta JSON Processing and Jakarta JSON Binding: Simplifies the parsing, generation, and binding of JSON data for Java objects, crucial for RESTful service communication. Jakarta REST: Provides a framework for creating web services according to the REST architectural pattern, enhancing web API development. Let’s delve deeper into some of the specifications included in the Jakarta Core Profile to understand their importance and functionality: This specification simplifies the code by reducing the need for external configuration files and making the intentions behind code clear. Annotations are extensively used across various Jakarta EE specifications. Simplification of Configuration: Annotations reduce the need for XML configuration files, making the setup more straightforward and less error-prone. Enhanced Readability and Maintenance: Code decorated with annotations is easier to read and maintain, as the configuration is co-located with the code it configures. Wide Adoption: Used across the Jakarta EE platform for a variety of purposes, including dependency injection, defining REST endpoints, and configuring beans. CDI is the specification that unifies the Jakarta EE platform by providing a consistent way to manage the lifecycle of stateful components and their interactions. The CDI Lite section of the specification is tailored for environments where full CDI support may be too heavyweight, such as microservices and serverless deployments. Type-safe Dependency Injection: Enables the injection of beans in a type-safe manner, reducing runtime errors and improving developer productivity. Contextual Lifecycle Management: Manages the lifecycle of beans according to well-defined contexts, simplifying state management across different scopes. Interceptors: Supports the use of interceptors for adding behavior to beans or for altering their behavior in a non-invasive manner. The CDI Lite Tutorial is an invaluable resource, if you are looking to gain a solid foundation in CDI Lite and its role within the Jakarta EE ecosystem, especially in the context of building lightweight microservices and cloud-native applications. It will take you through the basics, advanced features, and the practical application of CDI Lite, equipping you with the knowledge to make the most out of this powerful specification. Jakarta Interceptors allow developers to define methods that intercept business method invocations and lifecycle events on Jakarta EE components. This is particularly useful for implementing cross-cutting concerns such as logging, transactions, security, and more, without cluttering business logic. Separation of Concerns: Helps in separating cross-cutting concerns (like logging, transaction management, and security) from business logic. Reusability: Interceptors can be defined once and applied to multiple beans, promoting code reuse. Configurability: Interceptors can be enabled, disabled, or reordered through configuration, offering flexibility in their application. For an in-depth understanding of Jakarta Interceptors, We highly recommend you to read the Jakarta Interceptors Tutorial. This tutorial covers everything from basic concepts to advanced usage scenarios, providing a solid foundation for effectively utilizing interceptors in your projects. Jakarta JSON Processing (JSON-P) is a specification in the Jakarta EE platform that provides a portable API to parse, generate, transform, and query JSON data in a Java application. It is part of the larger ecosystem of Jakarta EE specifications designed to facilitate the development of enterprise applications with support for modern data formats and protocols, including JSON, which is widely used in web services and RESTful APIs. Parsing and Generation: JSON-P allows for both the parsing of JSON data into a Java representation and the generation of JSON data from Java objects. This can be done using either a streaming API for efficiency with large data sets or a more intuitive object model API for ease of use. Object Model API: This API provides a way to build or manipulate JSON data using a DOM-like tree structure. It enables developers to create, access, and modify JSON data in a flexible manner. Streaming API: The streaming API (JsonParser and JsonGenerator) offers a lower-level, event-based approach to parsing and generating JSON. It is highly efficient, making it suitable for processing large volumes of JSON data with minimal memory overhead. Data Binding: While JSON-P itself does not directly support data binding (converting between JSON and Java POJOs), it lays the groundwork for such functionality, which is further extended by Jakarta JSON Binding (JSON-B). For an in-depth exploration of Jakarta JSON Processing, including understanding JSON’s syntax, its applications in web services, and the programming models for manipulating JSON data, readers are encouraged to visit the Jakarta EE tutorial. This tutorial offers comprehensive guidance on both the object and streaming models for JSON data handling, suitable for beginners and advanced users alike. Learn more at the Jakarta EE Documentation on JSON Processing. Jakarta JSON Binding (JSON-B) is a specification within the Jakarta EE platform that provides a high-level API for converting (binding) Java objects to and from JSON documents. It sits on top of Jakarta JSON Processing (JSON-P) and offers a more convenient way to work with JSON data than manually parsing and generating JSON using JSON-P’s lower-level APIs. JSON-B is designed to simplify the task of serializing Java objects into JSON and deserializing JSON into Java objects, making it an essential tool for developing modern Java enterprise applications that interact with web services, RESTful APIs, and microservices. Automatic Binding: JSON-B can automatically bind Java objects to JSON and vice versa without requiring manual parsing, significantly simplifying code and reducing boilerplate. Customization: It provides annotations that allow developers to customize the serialization and deserialization process, such as changing property names in JSON, including or excluding specific fields, and handling custom data types. Support for Java Generics: JSON-B can handle complex objects, including those that use Java Generics, ensuring type safety during the binding process. Integration with JSON-P: JSON-B is built on top of JSON-P and can seamlessly integrate with it, allowing developers to mix high-level object binding with low-level JSON processing as needed. If you are interested in diving deeper into the specifics of JSON Binding, We highly recommend you to visit the Jakarta EE tutorial. It provides detailed insights into how JSON Binding works, including the processes for converting Java objects to JSON and vice versa. This knowledge is crucial for effectively managing JSON data in Java-based enterprise applications. Learn more at the Jakarta EE Documentation on JSON Binding. Jakarta RESTful Web Services(Jakarta REST) is a specification for creating web services according to the Representational State Transfer (REST) architectural pattern. It provides annotations to define resources and operations, making it straightforward to develop APIs for web applications. Annotation-driven Development: Simplifies the creation of web services by using annotations to define resources, HTTP methods, and response types. Flexible Data Format Support: While JSON is commonly used, JAX-RS supports a variety of data formats, providing flexibility in API design. Client API: Includes a client API for creating HTTP requests to RESTful services, facilitating communication between microservices. The Jakarta EE 10 Core Profile’s focus on these specifications underscores its aim to provide a lightweight, yet comprehensive platform for developing modern Java applications suited for microservices architectures and cloud-native environments. For those looking to master developing RESTful Web Services, we strongly encourage you to explore Jakarta RESTful Web Services Tutorial. This comprehensive tutorial offers a deep dive into the Jakarta RESTful Web Services specification, demonstrating how to create, deploy, and manage RESTful services efficiently. Jakarta Annotations and CDI plays a central role in integrating different Jakarta EE specifications, such as Jakarta Persistence API (formerly JPA) for database operations and Jakarta RESTful Web Services (formerly JAX-RS) for web services. Let’s now enhance the product microservices we developed previously. Jakarta Annotations is used for defining RESTful services and injecting dependencies. For instance, in our product microservices, we can update the Product and ProductRepository class to include annotations that facilitate entity management and dependency injection: package io.microprofile.tutorial.store.product.entity; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.GeneratedValue; import jakarta.validation.constraints.NotNull; @Entity @Table(name = \"Product\") @NamedQuery(name = \"Product.findAllProducts\", query = \"SELECT p FROM Product p\") @NamedQuery(name = \"Product.findProductById\", query = \"SELECT p FROM Product p WHERE p.id = :id\") @Data @AllArgsConstructor @NoArgsConstructor public class Product { @Id @GeneratedValue private Long id; @NotNull private String name; @NotNull private String description; @NotNull private Double price; } Explanation: @Entity and @Table(name = \"Product\"): These annotations declare the class as a Jakarta Persistence entity and map it to a database table named \"Product\". @Id and @GeneratedValue: These annotations denote the id field as the primary key of the entity and indicate that its value should be generated automatically. @NotNull: This annotation from Jakarta Bean Validation ensures that the name, description, and price fields cannot be null, enforcing data integrity at the application level. @NamedQuery: These annotations define Jakarta Persistence API named queries for common operations, such as retrieving all products or finding a product by its ids. These can be used throughout the application to interact with the database in a consistent manner. @Data, @AllArgsConstructor, and @NoArgsConstructor: These annotations from Project Lombok automatically generate boilerplate code such as getters, setters, a no-arguments constructor, and an all-arguments constructor. This keeps the entity class concise and focused on its fields and annotations related to Jakarta Persistence. The ProductRepository class serves as a bridge between the application’s business logic layer and the database, performing CRUD (Create, Read, Update, Delete) operations on Product entities. It exemplifies the separation of concerns, a fundamental principle in enterprise Java applications, by cleanly segregating the data access logic from the business logic. package io.microprofile.tutorial.store.product.repository; import java.util.List; import io.microprofile.tutorial.store.product.entity.Product; import jakarta.enterprise.context.RequestScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @RequestScoped public class ProductRepository { @PersistenceContext(unitName = \"product-unit\") private EntityManager em; private List products = new ArrayList<>(); public ProductRepository() { // Initialize the list with some sample products products.add(new Product(1L, \"iPhone\", \"Apple iPhone 15\", 999.99)); products.add(new Product(2L, \"MacBook\", \"Apple MacBook Air\", 1299.0)); } public void createProduct(Product product) { em.persist(product); } public Product updateProduct(Product product) { return em.merge(product); } public void deleteProduct(Product product) { em.remove(product); } public List findAllProducts() { return em.createNamedQuery(\"Product.findAllProducts\", Product.class).getResultList(); } public Product findProductById(Long id) { return em.find(Product.class, id); } public List findProduct(String name, String description, Double price) { return em.createNamedQuery(\"Event.findProduct\", Product.class) .setParameter(\"name\", name) .setParameter(\"description\", description) .setParameter(\"price\", price).getResultList(); } } Explanation: ProductRepository: This class utilizes Jakarta Persistence API (JPA) for database operations, encapsulating the CRUD (Create, Read, Update, Delete) operations along with methods to find products by various criteria. @RequestScoped: This CDI annotation for ProductRepository class indicates that an instance of this class is created for each HTTP request to ensure that database operations are handled within the context of a single request. @PersistenceContext: This annotation injects an entity manager instance, em, specifying the persistence unit product-unit. The entity manager is the primary JPA interface for database interactions. The methods createProduct(), updateProduct(), deleteProduct(), findAllProducts(), and findProductById() methods define CRUD operations that might be performed by the repository. These methods utilize the EntityManager instance to persist, merge, remove, and query for product entities. The EntityManager is responsible for managing the persistence context and performing CRUD operations on the entities. The ProductRepository serves as a foundational example for developers to understand how to construct a data access layer in a MicroProfile application, emphasizing the significance of CDI in managing component lifecycles and dependencies, as well as showcasing the application of Jakarta Persistence for Object Relational Mapping(ORM) based data access. CDI defines several built-in scopes to manage the lifecycle of beans, each corresponding to a specific context within the application. When a bean is needed, the CDI container automatically creates it within its defined scope, manages its lifecycle, and destroys it when the context ends. This process is largely transparent to the developer, simplifying development. To learn more about using built-in scopes in CDI for the lifecycle management of beans, We highly recommend visiting the Using Scopes section of the Jakarta EE Tutorial. This resource provides valuable insights into each scope and how to use them effectively in your applications. Jakarta RESTful Web Services annotations are utilized to define endpoints for the web services, facilitating the creation and management of RESTful APIs. The ProductResource class demonstrates this: package io.microprofile.tutorial.store.product.resource; import java.util.List; import io.microprofile.tutorial.store.product.entity.Product; import io.microprofile.tutorial.store.product.repository.ProductRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; @Path(\"/products\") @ApplicationScoped public class ProductResource { private static final Logger LOGGER = Logger.getLogger(ProductResource.class.getName()); // ... @Inject private ProductRepository productRepository; @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllProducts() { LOGGER.info(\"Fetching all products\"); return Response.ok(products).build(); } // Additional endpoint methods } The @ApplicaitonScoped is an CDI annotation that specifies that the bean is application-scoped, meaning there will be a single instance of ProductResource for the entire application, which promotes better resource utilization and performance. The @Inject annotation is commonly used in CDI to inject instances into the application classes without needing to do manual lookups or new instance creations. For example, When ProductResource needed a repository to fetch products from a database, we used @Inject to incorporate that repository seamlessly. @Path and @GET: Defines the URI path and HTTP method for accessing the getProducts endpoint. When creating a REST API, you typically start by defining the resources that your API will expose. A unique URI identifies each resource. You then define the operations that can be performed on each resource. These operations are typically CRUD operations: create, read, update, and delete. Let us now create a RESTful API to manage a list of products for a store. This RESTful API allows client applications to access the product stored as resources on the server. The API is implemented using Jakarta EE and REST architectural style. The API has the following methods: GET /api/products: Retrieves a list of products POST /api/products: Creates a new product, the product details are provided as JSON in the request body PUT /api/products: Updates an existing product, the updated product details are provided as JSON in the request body DELETE /api/products/chapter03: Deletes a product, the product id is provided in the request URL path Multiple annotations can be used together in a single method to support multiple media types. For example, When both @Consumes(MediaType.APPLICATION_JSON) and @Produces(MediaType.APPLICATION_XML) are used together in a single method, then the method can consume JSON and produce XML. Table 3-1 shows a list of some of the popular Media types along with their constant fields in jakarta.ws.rs.core.MediaType class and corresponding HTTP ContentType: Media Type Constant Field Description application/json MediaType.APPLICATION_JSON JSON format, used for representing structured data. application/xml MediaType.APPLICATION_XML XML format, used for representing structured data in XML format. text/xml MediaType.TEXT_XML XML format, primarily used for XML data that is human-readable. text/plain MediaType.TEXT_PLAIN Plain text format, used for unstructured text data. text/html MediaType.TEXT_HTML HTML format, used for markup data that can be rendered by web browsers. application/octet-stream MediaType.APPLICATION_OCTET_STREAM Binary data stream, used for transmitting files or streaming. application/x-www-form-urlencoded MediaType.APPLICATION_FORM_URLENCODED Web form format, used for submitting form data in HTTP requests. multipart/form-data MediaType.MULTIPART_FORM_DATA Multipart format, used for uploading files through web forms. application/vnd.api+json Custom JSON API format, a specification for how clients should request and modify resources. application/hal+json Custom Hypertext Application Language (HAL) JSON format, used for linking between resources in APIs. After having successfully performed the development and testing of the GET method of ProductResource to fetch the list of product resources. Let’s now call the create, update and delete methods for our Products REST API. For this you only need to add additional methods of our ProductResource class. @POST @Consumes(MediaType.APPLICATION_JSON) @Transactional public Response createProduct(Product product) { System.out.println(\"Creating product\"); productRepository.createProduct(product); return Response.status(Response.Status.CREATED) .entity(\"New product created\").build(); } Explanation: The createProduct() method is annotated with @POST, which means it can be invoked via an HTTP POST request. The @Consumes(MediaType.APPLICATION_JSON) annotation says it will consume JSON data. This method takes a single parameter, which is of type Product`. This parameter will be populated with the data sent in the HTTP POST request. The method creates a new Product object and adds it to the list of products. Finally, the method returns a Response object with a status code of 201 (Created) and a message indicating that a new product has been created. You can use a REST client such as Postman or the cURL command line utility to test the HTTP methods (including PUT, POST, DELETE). To verify the POST request, you can use the following cURL command. This sends a JSON object representing a new product to your microservice. Command: $ curl -H 'Content-Type: application/json' -d '{ \"id\": \"3\", \"name\":\"iPhone 14\", \"description\":\"Apple iPhone 14\", \"price\":\"799.99\"}' -X POST http://localhost:9080/mp-ecomm-store/api/products Output: New product created This command specifies the content type as JSON and sends a data payload representing a product with an ID of 3, the name \"iPhone 14\", a description of \"Apple iPhone 14\", and a price of 799.99. The -X POST parameter indicates that this is a POST request. Upon successful execution, your service should process this data and add the new product to the database. Next you can verify the addition of the new product, by calling the GET method using cURL or browser as described previously to list all products. This request should now return an updated list of products, including the newly added product. $ curl http://localhost:9080/mp-ecomm-store/api/products Updating existing product information is a common operation for RESTful services managing a catalog of items. The PUT request method is designed for these scenarios, allowing you to modify an existing product’s details. The code snippet below demonstrates updating the product: @PUT @Consumes(MediaType.APPLICATION_JSON) @Transactional public Response updateProduct(Product product) { // Update an existing product Response response; System.out.println(\"Updating product\"); Product updatedProduct = productRepository.updateProduct(product); if (updatedProduct != null) { response = Response.status(Response.Status.OK) .entity(\"Product updated\").build(); } else { response = Response.status(Response.Status.NOT_FOUND) .entity(\"Product not found\").build(); } return response; } Explanation: The @PUT annotation defines that the method updateProduct() can be invoked via an HTTP PUT request. As in the POST method, the @Consumes(MediaType.APPLICATION_JSON) annotation specifies the method will consume JSON data. This method takes a single parameter, which is of type Product. This parameter will be populated with the data sent in the HTTP PUT request. The method updates the product with the same id as the one sent in the request. If a product with the same id is not found, the method returns a 404 (Not Found) error. Finally, the method returns a Response object with a status code of 204 (No Content) and a message indicating that an existing product has been updated. To test the PUT request, you can use the following cURL command. $ curl -H 'Content-Type: application/json' -d '{ \"id\": \"3\",\"name\":\"iPhone14\", \"description\":\"Apple iPhone 14\", \"price\":\"749\"}' -X PUT http://localhost:5050/mp-ecomm-store/api/products Next you can verify the updation of the new product, by calling the GET method using cURL or browser as described previously to list all products. $ curl http://localhost:9080/mp-ecomm-store/api/products @DELETE @Path(\"products/{id}\") public Response deleteProduct(@PathParam(\"id\") Long id) { // Delete a product Response response; System.out.println(\"Deleting product with id: \" + id); Product product = productRepository.findProductById(id); if (product != null) { productRepository.deleteProduct(product); response = Response.status(Response.Status.OK) .entity(\"Product deleted\").build(); } else { response = Response.status(Response.Status.NOT_FOUND) .entity(\"Product not found\").build(); } return response; } Explanation: The @DELETE annotation defines that the method deleteProduct() can be invoked via an HTTP DELETE request. The @Path annotation specifies the id path parameter that will be used to identify which product to delete. This method takes a single parameter of type Long and is annotated with the @PathParam annotation. This parameter will be populated with the id path parameter from the HTTP DELETE request. The method deletes the product with the same id as the one sent in the request. If a product with the same id is not found, the method returns a 404 (Not Found) error. Finally, the method returns a Response object with a status code of 204 (No Content) and a message indicating that an existing product has been deleted. The code demonstrated in this chapter is not production quality. It was highly simplified to explain to you the fundamental principles of the REST API. In the upcoming chapters, you will be further building upon this code. By implementing many features from the latest MicroProfile and Jakarta EE standards, you would be making it more a robust microservice that is also cloud-ready. You would also learn to containerize, scale, deploy and manage this application. This chapter has laid a solid foundation on the Jakarta EE 10 Core Profile, emphasizing its crucial role in the development of microservices using MicroProfile. By delving into key specifications and through practical implementation examples, you have been equipped with the necessary knowledge to utilize the Jakarta EE 10 Core Profile’s features for creating scalable, resilient, and portable cloud-native applications. Additionally, this chapter guided you through the creation of RESTful web services using Jakarta EE Restful Web Services APIs, providing an overview of REST (Representational State Transfer), it aimed to familiarize you with the basics of REST, enabling you to create and deploy a RESTful web service independently. As we move forward, the next chapter will delve deeper into the REST architectural pattern, exploring standard conventions, design considerations, and best practices. It will cover many advanced concepts essential for building RESTful web services tailored for cloud-native and microservices-based applications, preparing you for more sophisticated aspects of modern application development.","title":"Jakarta EE 10 Core Profile","component":"microprofile-tutorial","version":"6.1","name":"chapter03","url":"/microprofile-tutorial/6.1/chapter03/chapter03.html","titles":[{"text":"Introduction","hash":"introduction","id":1},{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":2},{"text":"Understanding the Jakarta EE 10 Core Profile","hash":"understanding-the-jakarta-ee-10-core-profile","id":3},{"text":"Key Specifications in Jakarta EE 10 Core Profile","hash":"key-specifications-in-jakarta-ee-10-core-profile","id":4},{"text":"Jakarta Annotations","hash":"jakarta-annotations","id":5},{"text":"Key Features","hash":"key-features","id":6},{"text":"Jakarta Contexts and Dependency Injection (CDI) - CDI Lite","hash":"jakarta-contexts-and-dependency-injection-cdi-cdi-lite","id":7},{"text":"Key Features of Contexts and Dependency Injection (CDI) - CDI Lite","hash":"key-features-of-contexts-and-dependency-injection-cdi-cdi-lite","id":8},{"text":"Jakarta Interceptors","hash":"jakarta-interceptors","id":9},{"text":"Key Features of Jakarta Interceptors","hash":"key-features-of-jakarta-interceptors","id":10},{"text":"Jakarta JSON Processing","hash":"jakarta-json-processing","id":11},{"text":"Key Features of Jakarta JSON Processing","hash":"key-features-of-jakarta-json-processing","id":12},{"text":"Jakarta JSON Binding","hash":"jakarta-json-binding","id":13},{"text":"Key Features of Jakarta JSON Binding","hash":"key-features-of-jakarta-json-binding","id":14},{"text":"Jakarta RESTful Web Services","hash":"jakarta-restful-web-services","id":15},{"text":"Key Features of Jakarta RESTful Web Services","hash":"key-features-of-jakarta-restful-web-services","id":16},{"text":"Managing Component Dependencies","hash":"managing-component-dependencies","id":17},{"text":"Entity class","hash":"entity-class","id":18},{"text":"Repository class","hash":"repository-class","id":19},{"text":"Lifecycle Management of Beans in Jakarta EE","hash":"lifecycle-management-of-beans-in-jakarta-ee","id":20},{"text":"Handling HTTP Methods and Resources","hash":"handling-http-methods-and-resources","id":21},{"text":"Defining RESTful APIs","hash":"defining-restful-apis","id":22},{"text":"Implementing REST APIs for Managing Products Data","hash":"implementing-rest-apis-for-managing-products-data","id":23},{"text":"Creating a Product","hash":"creating-a-product","id":24},{"text":"Verifying the POST request","hash":"verifying-the-post-request","id":25},{"text":"Updating a Product","hash":"updating-a-product","id":26},{"text":"Verifying the PUT request","hash":"verifying-the-put-request","id":27},{"text":"Deleting a Product","hash":"deleting-a-product","id":28},{"text":"Summary","hash":"summary","id":29}]},"13":{"id":13,"text":"In the previous chapter, we saw how RESTful APIs facilitate language-agnostic access to web services from diverse environments. However, a clear and comprehensive contract is required to ensure seamless integration between clients and services. This need for a well-defined API contract has led to the adoption of the OpenAPI specification. This chapter will explore the primary features of MicroProfile OpenAPI, demonstrate how to integrate it into your MicroProfile applications, and show you how to annotate your RESTful services to produce rich documentation that adheres to the OpenAPI specification. Furthermore, we will introduce the OpenAPI UI, a visual interface allowing developers and stakeholders to interact with and visualize the documented APIs, enhancing understanding and facilitating integration. Introduction to MicroProfile OpenAPI API Specification using MicroProfile Open API Generating API Documentation Documenting Authentication and Authorization Requirements Exploring the APIs using Swagger UI The Open API Specification (OAS), formerly Swagger specification, is a technical specification that allows REST API providers to describe and publish their APIs using a format that various tools can consume. It defines a standard, language-agnostic interface to RESTful APIs, making it easy for third-party tools to generate documentation, client SDKs, and a range of tools that promote the seamless consumption of RESTful APIs. The OpenAPI Initiative, a consortium of industry experts committed to standardizing how to describe REST APIs, maintains the OpenAPI Specification. It is a community-driven initiative, and many large organizations use it, including Google, Microsoft, and Amazon. The OpenAPI specification enables creation of a well-defined, clear and comprehensive API contract. It provides a standardized way to describe the API’s structure, expected requests and responses, and authentication mechanisms, making it easier to develop, test, and maintain RESTful APIs. The MicroProfile OpenAPI specification builds upon the widely recognized OpenAPI Specification (OAS) and leverages annotations from the Jakarta Restful Web Services specification. The primary focus of MicroProfile OpenAPI is on defining REST APIs that utilize JSON within the context of HTTP. The specification aims to provide a uniform way of describing APIs so that they are both human-readable and machine-readable. It facilitates the creation of APIs that are consistent, well-documented, and easily consumable by both humans and machines. MicroProfile OpenAPI provides a suite of Java APIs that allows developers to define and generate API specifications that adhere to OpenAPI v3 standards. As a result, it simplifies the process of designing, documenting, and publishing RESTful APIs for developers. Developers can quickly generate documentation for their microservices using MicroProfile OpenAPI. The documentation includes information on what services are provided, how to invoke them, and what data types are used. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools. Also, documentation can generate client code to access the web services. The OpenAPI Specification fuels a rich ecosystem of tools that automate and support. This specification streamlines the creation of OpenAPI documentation for RESTful services using a unified approach. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools: API Documentation Generation: Intuitive interactive documentation portals emerge directly from the specification. Client SDK Creation: Client libraries in various languages can be automatically generated. API Testing: Testing frameworks can leverage the specification to design robust tests. API Mocking: Simplifies mocking APIs for testing and development purposes. There are multiple ways in which you can generate OpenAPI documents. The most common way is to use annotations. This only requires augmenting your Jakarta Restful Web Services annotations with OpenAPI annotations. Besides annotations, a predefined OpenAPI document may be provided in either YAML or JSON format. This so-called static model will be merged with the model generated by scanning for Jakarta REST endpoints and the combined result will be made available to clients. However, the annotation-based approach is recommended as it is more maintainable and easier to understand. Finally, you can filter out the resources you do not want to document using configuration. To document Jakarta RESTful Web Services using MicroProfile OpenAPI, we need to annotate the resource classes and methods with the OpenAPI annotation. To use MicroProfile OpenAPI in your project, you need to add the following maven coordinates to your project: org.eclipse.microprofile.openapi microprofile-openapi-api 3.1.1 Below is an illustrative example of how you might annotate a method in the ProductResource class to achieve this documentation using MicroProfile OpenAPI annotations: import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @ApplicationScoped @Path(\"/products\") @Tag(name = \"Product Resource\", description = \"CRUD operations for products\") public class ProductResource { //... @GET @Produces(MediaType.APPLICATION_JSON) @Operation(summary = \"List all products\", description = \"Retrieves a list of all available products\") @APIResponses(value = { @APIResponse( responseCode = \"200\", description = \"Successful, list of products found\", content = @Content(mediaType = \"application/json\", schema = @Schema(implementation = Product.class)) ), @APIResponse( responseCode = \"400\", description = \"Unsuccessful, no products found\", content = @Content(mediaType = \"application/json\") ) }) public List getAllProducts() { // Method implementation } } Explanation: @Operation: Provides a summary and description for the getProducts() method. @APIResponse: Describes the possible responses from the getProducts() operation. In this case, a successful response (HTTP 200) is described, indicating that the method returns an array of Product entities. @Schema: Specifies the schema of the response content. Here, it is used to indicate that the method returns an array of Product objects. These annotations enrich the ProductResource class with metadata necessary for generating comprehensive and descriptive OpenAPI documentation automatically. We have also annotated the getProducts() method with the @APIResponse annotation to document the successful response from the operation. The responseCode field is used to specify the status code of the response, and the description field is used to provide a brief description of the response. There are two possible responses – a successful response containing a list of produdts with a 200 status code, and an unsuccessful response with a 400 status code, if no products are found. The content field is used to specify the schema of the response content. In this example, the response content is a list of `Product`s. Finally, we need to add the following property to the src/main/resources/META-INF/microprofile-config.properties file: mp.openapi.scan=true This property tells MicroProfile OpenAPI to scan our classes for annotations and generate API documentation for them. Now that we have configured MicroProfile OpenAPI, we can build and run our application. To view the generated documentation, we can use the OpenAPI UI tool. The Open API UI tool is a web-based tool that can be used to view the documentation for a REST API. The OpenAPI UI tool can be accessed at the following URL: http://localhost:/openapi/ Replace with the actual port used by your runtime, for e.g. 9080 which is the default port at Open Liberty server. The /openapi endpoint is used to get information about the OpenAPI specification generated from the comments in the source code annotations. It returns information in YAML format. When we access the http://localhost:5050/openapi URL, we should see the API documentation that was generated by MicroProfile OpenAPI: openapi: 3.0.3 info: title: Generated API version: \"1.0\" servers: - url: http://localhost:9080/catalog paths: /api/products: get: responses: \"200\": description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Product' put: requestBody: content: application/json: schema: $ref: '#/components/schemas/Product' responses: \"200\": description: OK post: requestBody: content: application/json: schema: $ref: '#/components/schemas/Product' responses: \"200\": description: OK /api/products/products/{id}: delete: parameters: - name: id in: path required: true schema: format: int64 type: integer responses: \"200\": description: OK /api/products/{id}: get: parameters: - name: id in: path required: true schema: format: int64 type: integer responses: \"200\": description: OK content: application/json: schema: $ref: '#/components/schemas/Product' components: schemas: Product: required: - name - description - price type: object properties: id: format: int64 type: integer name: type: string description: type: string price: format: double type: number As we can see, MicroProfile OpenAPI has generated API documentation for our resource class. We can use this documentation to learn about the API and how to use it. MicroProfile OpenAPI allows developers to produce these specifications directly from their codebase, leveraging annotations and/or providing OpenAPI documents statically. This direct generation ensures that the API documentation is always up to date with the code. To open Swagger UI for the API documentation generated using MicroProfile OpenAPI, you will need to deploy your application to a server that supports MicroProfile, such as Open Liberty, WildFly, Quarkus, or Payara Micro. These servers automatically generate the OpenAPI documentation for your RESTful services based on the annotations in your code. Next, visit the following URL to launch the Swagger UI: http://localhost:9080/openapi/ui Swagger UI is then used to render this documentation in a user-friendly web interface. Below is the screenshot of swagger UI for the Product REST Resource. Swagger UI 1. Swagger UI The MicroProfile OpenAPI annotations can be used to document any Jakarta Restful Web Services resource. The annotations can also be used in conjunction with other Jakarta Restful Webservices annotations, such as @Path and @Produces. The most common annotations that are used to document RESTful web services are list in Table 4-1. Annotations Details @OpenAPIDefinition Provides metadata about the entire API. It can include information such as the title, description, version, terms of service, and contact information. @Info Used inside @OpenAPIDefinition to provide API metadata like title, version, description. @Contact Specifies contact information for the API, used within @Info. @License Defines the license information for the API, also used within @Info. @Operation Describes a single API operation on a resource. @APIResponse It is used to document a response from an operation. @APIResponses A container for multiple @APIResponse annotations, allowing documentation of different responses for a single API operation. @RequestBody Describes the request body of an HTTP request, specifying the content of the body and whether it is required. @Schema Provides schema details for a response or request body, specifying the data type, format, and constraints. @Parameter Provides information on parameters to the operation, including query parameters, header parameters, and path parameters. @Tag Adds metadata to a single tag that is used by the Operation. It helps in categorizing operations by resources or any other qualifier. @Content Specifies the media type and schema of the operation’s request or response body. @Components Allows the definition of reusable components such as schemas, responses, parameters, and more, which can be referenced by other annotations. @SecurityRequirement Specifies a security requirement for an operation, referencing security schemes defined in the @Components. @ExternalDocumentation Provides additional external documentation for an API or operation. @Callback Specifies a callback URL for an asynchronous operation. @Callbacks Specifies multiple @Callback annotations. @Server Describes a server that hosts the API, specifying URL and description, which can be global or specific to operations or paths All of these annotations are defined in the org.eclipse.microprofile.openapi.annotations package. By integrating the MicroProfile OpenAPI, developers can generate detailed, OpenAPI-compliant documentation automatically, fostering better understanding and interaction among services. By annotating ProductResource class, we generated API documentation as per Open API specification. This will ensure the services are readily discoverable, understandable, and usable, thereby accelerating development cycles and fostering a more robust and collaborative developer ecosystem.","title":"MicroProfile OpenAPI","component":"microprofile-tutorial","version":"6.1","name":"chapter04","url":"/microprofile-tutorial/6.1/chapter04/chapter04.html","titles":[{"text":"Introduction","hash":"introduction","id":1},{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":2},{"text":"OpenAPI Specification","hash":"openapi-specification","id":3},{"text":"Introduction to MicroProfile OpenAPI","hash":"introduction-to-microprofile-openapi","id":4},{"text":"Capabilities of MicroProfile OpenAPI Specification","hash":"capabilities-of-microprofile-openapi-specification","id":5},{"text":"Generating OpenAPI documents","hash":"generating-openapi-documents","id":6},{"text":"Using MicroProfile Open API in your project","hash":"using-microprofile-open-api-in-your-project","id":7},{"text":"How to view the generated documentation","hash":"how-to-view-the-generated-documentation","id":8},{"text":"Exploring the APIs using Swagger UI","hash":"exploring-the-apis-using-swagger-ui","id":9},{"text":"Annotations","hash":"annotations","id":10},{"text":"Summary","hash":"summary","id":11}]},"14":{"id":14,"text":"This chapter focuses on MicroProfile Configuration, a key feature that allows developers to externalize configuration properties from their code. You can adapt configuration parameters to different environments (development, testing, production) without altering the core code. It provides flexibility and adaptability for microservices in different environments. Understanding MicroProfile Configuration Different environments required for Microservices development Working with Various Configuration Sources Key capabilities of MicroProfile Configuration Implementing Configuration Properties Creating a Custom Configuration Source Dynamic Updates and Handling Configuration Change Events Managing Configuration for Different Environments Securing Configuration and Best Practices MicroProfile Configuration is a specification that allows developers to inject configuration values into applications. The MicroProfile Configuration APIs will enable developers to externalize configuration and access it from within your application. By separating configuration data (like database URLs, API credentials, feature flags) from the codebase, you make it easier to modify these settings without recompiling and redeploying the application. For instance, with MicroProfile Config, you can configure connection settings for a database enhancing flexibility and adaptability across different environments in our MicroProfile e-commerce application. You can update configurations seamlessly, sometimes even while the application is running (for dynamic config sources), minimizing downtime and streamlining deployment processes. This is essential for microservices that may run in diverse setups. When developing microservices, it’s essential to establish various environments to accommodate different stages of the development lifecycle. Each environment serves distinct purposes, ensuring the application is thoroughly tested, secure, and efficient before its deployment to production. Below are the critical environments typically set up for developing microservices: Development Environment—Developers write new code, implement features, and perform basic unit testing in this environment, which is where the initial development occurs. This environment is usually configured to use local or development databases with dummy data for testing. The logging level used in this environment is generally verbose for debugging purposes. Testing or QA Environment—This environment is dedicated to rigorous testing, including automated tests, integration tests, and manual testing by QA engineers to identify bugs or issues. Configuration here mirrors production settings as closely as possible and connects to a testing database. For error tracing, detailed logging may be enabled in this environment. Staging Environment—This is a production-like environment for final testing of the changes before deployment to production. It ensures that your microservices perform as expected under production conditions. This environment is configured with settings identical to the production environment. It typically uses a copy of production data that is sanitized of sensitive data. Production Environment—This is the live environment where the microservice is fully deployed and accessible to end-users. It’s optimized for security, performance, and reliability and configured to access actual user data with all security features fully enabled. Performance monitoring tools are also set up here to ensure smooth operations. Using the above set of environments, development teams can streamline the development process, enhance quality, and ensure the microservices are robust and ready for production use. Your development team may also require additional environments for specific needs like automation, penetration testing, and stress testing, depending on the unique needs of the microservices. MicroProfile Config allows applications to retrieve configuration properties from a variety of sources. By default, MicroProfile Config includes various built-in configuration sources, but you can also define custom sources. Below we discuss how to work with these various configuration sources. MicroProfile Config defines default configuration sources that are automatically enabled: System Properties: Configuration values defined as system properties can be accessed by MicroProfile Config. These properties can be set at runtime using the -D flag when starting the JVM. Environment Variables: Environment variables available in the system can be used as configuration sources. They are useful for setting configuration properties external to the application, especially in containerized environments. MicroProfile Config Properties File: A properties file named microprofile-config.properties can be placed in the META-INF directory of your application. This file is particularly useful for setting default configuration values that ship with the application. A static configuration source is the one where the data does not change once the application has started. Examples include the microprofile-config.properties file and most custom implementations that read from a database or a service at startup. On the other hand, a dynamic configuration source is one that can change its data at runtime. System properties and some custom implementations that periodically check for changes in a remote configuration service are examples of dynamic sources. MicroProfile Config allows applications to read from these dynamic sources as easily as from static ones. However, whether a configuration source supports dynamic behavior depends on its implementation. The MicroProfile Configuration specifications offer a set of APIs that enable you to handle your application’s configuration efficiently. They allow you to easily manage and customize your application’s configurations, making it a valuable tool for developers. The MicroProfile Configuration APIs provide the following capabilities for managing the configuration settings of your application: It allows reading configuration values. It allows applications to retrieve configuration values reliably, supporting various sources, such as property files, system properties, environment variables, and more. The MicroProfile Configuration API provides several classes, allowing easy integration of configuration values. Below is the list of key classes and interfaces included in the MicroProfile Configuration API: Config - the class that is the main entry point to the configuration API and provides access to configuration data. The Config class provides static methods that can be used to access configuration properties. ConfigProvider - a utility class for getting the Config instance. It allows retrieving the static instance of the Config object. ConfigBuilder - An interface used to create a Config instance manually. It can add default sources, converters, and configuration sources. ConfigSource - This class represents a source of configuration values. It reads configuration data from a specific source, such as system properties, environment variables, files, or data stores. Property - It represents a key/value pair in the configuration data. Converter - This interface implements custom converters that convert configuration values from String to any desired type. These classes and interfaces provide a robust configuration mechanism that is easy to use and extend. Developers can leverage these APIs to externalize configuration from their applications, making them more flexible and more accessible to run in different environments. The Config API allows you to define configuration properties in many ways, including property files, environment variables, and system properties. To use the Config API, we’ll need to include the following dependency in our pom.xml file: org.eclipse.microprofile.config microprofile-config-api 3.1 For Gradle, modify your build.gradle file with the following dependency: implementation 'org.eclipse.microprofile.config:microprofile-config-api:3.1' Let’s now modify the getProducts() method to return a jakarta.ws.rs.core.Response object instead of a list of Product entities directly, we can utilize the Response class to build our response. This approach allows for a more standardized and flexible API response handling, including the ability to set HTTP status codes and headers. Lets create a configuration file with the name microprofile-config.properties and the content as below: # microprofile-config.properties product.maintenanceMode=false This configuration file should be placed in the src/main/resources/META-INF/ directory of your application. Next inject this configuration value to a private variable in the ProductResource and consume this within all the operations of this service. MicroProfile Config will automatically detect and use the properties defined in this file, allowing you to externalize configuration and easily adjust the behavior of your application based on the environment in which it is deployed. Below is the updated ProductResource class and getProducts() method: package io.microprofile.tutorial.store.product.resource; import io.microprofile.tutorial.store.product.entity.Product; import io.microprofile.tutorial.store.product.repository.ProductRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import java.util.List; @Path(\"/products\") @ApplicationScoped public class ProductResource { @Inject @ConfigProperty(name=\"product.maintenanceMode\", defaultValue=\"false\") private boolean maintenanceMode; @Inject private ProductRepository productRepository; @GET @Produces(MediaType.APPLICATION_JSON) @Transactional // OpenAPI code // … public Response getProducts() { List products = productRepository.findAllProducts(); // If in maintenance mode, return Service Unavailable status if (maintenanceMode) { return Response .status(Response.Status.SERVICE_UNAVAILABLE) .entity(\"The product catalog service is currently in maintenance mode. Please try again later.\") .build(); // If products found, return products and OK status } else if (products != null && !products.isEmpty()) { return Response .status(Response.Status.OK) .entity(products).build(); // If products not found, return Not Found status and message } else { return Response .status(Response.Status.NOT_FOUND) .entity(\"No products found\") .build(); } } } Explanation: @Inject: This CDI annotation enables dependency injection. It tells the container to inject an instance of a specified bean at runtime. As we have learnt previously, dependency injection enables loose coupling between classes and their dependencies, making the code more modular, easier to test, and maintain. @ConfigProperty(name=\"product.maintenanceMode\", defaultValue=\"false\"): This MicroProfile Config annotation used along with @Inject to inject configuration property values into beans. It allows developers to externalize configuration from the application code, making applications more flexible and environment-agnostic. The name parameter specifies the key of the configuration property to be injected. In this case, product.maintenanceMode is the key for a property that controls whether this service is in maintenance mode. The defaultValue provides a default value to be used if the specified configuration property is not found in any of the configured sources. Here, the default value is false, indicating that, by default, the service is not in maintenance mode unless explicitly configured otherwise. private boolean maintenanceMode: This field is set to the value of the product.maintenanceMode configuration property. Due to the defaultValue =\"false\", if the configuration is not specified elsewhere, maintenanceMode will be false, meaning the service operates normally. private ProductRepository productRepository: This field is injected with an instance of ProductRepository. This class abstracts the data access operations for products. This injection decouples the class from the specific implementation of the repository, making the code more modular and easier to adapt or replace parts of it in the future. The getProducts() method retrieves all products from the repository by calling productRepository.findAllProducts(), which queries the database to retrieve a list of all available products. Before proceeding to return the list of products, the method checks the maintenanceMode flag. If maintenanceMode is true, the service is currently undergoing maintenance, and thus, it is not appropriate to perform regular operations. The method constructs and returns a Response with a 503 Service Unavailable HTTP status code, along with a message indicating that the product catalog service is in maintenance mode. If the service is not in maintenance mode, then the method checks if the list of retrieved products is not null and not empty. If products are found, it constructs a Response with a status of 200 OK and includes the list of products as the response entity. This indicates a successful operation where product data is found and returned. If the products list is`null` or empty, indicating no products were found, the method constructs and returns a Response with a 404 Not Found status code and a message stating that no products were found. When we deploy the application and invoke the /api/products endpoint, we should see the list of products as below: [{\"description\":\"Apple iPhone 15 Pro\",\"id\":1,\"name\":\"iPhone 15 Pro\",\"price\":999.0}] For non-critical properties, providing a default value using the defaultValue attribute of the @ConfigProperty annotation ensures that your application has a fallback option. We can specify a default value to be used if the property does not exist as below: public class ProductResource { @Inject @ConfigProperty(name=\"product.maintenanceMode\", defaultValue=\"false\") private boolean maintenanceMode; … In the example above , the false default value will be used if the product.maintenanceMode property does not exist. ConfigProperty also supports type conversion, so we can inject our configuration data into fields of any type: @Inject @ConfigProperty(name=\"product.maintenanceMode\", defaultValue=\"false\") private boolean maintenanceMode; In this example, the product.maintenanceMode property will be converted to an Boolean before it is injected into the maintenanceMode field. We can also use the Config API to convert our configuration data to a POJO: import org.eclipse.microprofile.config.inject.ConfigProperty; public class MyApplication { @Inject private MaintenanceMessage message; } public class MaintenanceMessage { @ConfigProperty(name=\"product.maintenanceMessage\") private String message; } In this example, we’re injecting a property named \"product.maintenanceMessage\" into the message field of our MaintenanceMessage class. As we saw, the Config API makes it easy to inject configuration properties into an application. The Config API defines a contract for config implementations. A ConfigSource is used to read configuration data from a particular source. For example, we could create a ConfigSource that reads configuration data from a file. ConfigSource interface has the following methods: String getName() : Returns the name of the ConfigSource. int getOrdinal() : Returns the ordinal of the ConfigSource. Ordinals are used to determine the precedence of ConfigSources. A higher ordinal means a higher precedence. Map getProperties() : Returns a map of the properties in this ConfigSource. The keys in the map are the property names, and the values are the property values. getValue(String propertyName) : Returns the value of the given property. If the property is not found, this method returns null. Set getPropertyNames() : Returns a Set of the property names in this ConfigSource. Let’s implement a feature in our MicroProfile e-Commerce application to integrate payment gateway configuration dynamically by creating a PaymentServiceConfigSource (a custom ConfigSource) which could fetch API keys and endpoints. This would ensure that payment service configurations are up-to-date and can be changed without redeploying the application. The following is an implementation of a ConfigSource that reads configuration data from a file: package io.microprofile.tutorial.store.payment.config; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.microprofile.config.spi.ConfigSource; public class PaymentServiceConfigSource implements ConfigSource{ private Map properties = new HashMap<>(); public PaymentServiceConfigSource() { // Load payment service configurations dynamically // This example uses hardcoded values for demonstration properties.put(\"payment.gateway.apiKey\", \"secret_api_key\"); properties.put(\"payment.gateway.endpoint\", \"https://api.paymentgateway.com\"); } @Override public Map getProperties() { return properties; } @Override public String getValue(String propertyName) { return properties.get(propertyName); } @Override public String getName() { return \"PaymentServiceConfigSource\"; } @Override public int getOrdinal() { // Ensuring high priority to override default configurations if necessary return 600; } @Override public Set getPropertyNames() { // Return the set of all property names available in this config source return properties.keySet();} } The above code snippet demonstrates MicroProfile Config’s flexibility in integrating with various external configuration providers. This enables applications to load configurations from sources beyond the default system properties, environment variables, and microprofile-config.properties files. This capability is crucial for modern applications that may need to pull configuration from dynamic sources like cloud services, databases, or custom APIs. When integrating with external configuration providers, it’s essential to consider security aspects, especially when dealing with sensitive configuration data. Use secure communication channels (e.g., HTTPS) to retrieve configuration from external services. Manage access control meticulously to prevent unauthorized access to sensitive configuration. Consider encrypting sensitive configuration values and decrypting them within your ConfigSource or application logic. To register a custom ConfigSource implementation with MicroProfile Config, you need to include the fully qualified class name of your custom ConfigSource in this resource file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource. This PaymentService would be a part of the e-Commerce application, handling payment transactions by utilizing configurations that determine which payment gateway to use and how to authenticate with it. By externalizing these configurations, the e-Commerce platform can easily switch payment providers or update API keys without needing to adjust the codebase, providing flexibility and enhancing security. First, create a class to represent the payment information sent by clients as below: package io.microprofile.tutorial.store.payment.entity; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class PaymentDetails { private String cardNumber; private String cardHolderName; private String expirationDate; // Format MM/YY private String securityCode; private BigDecimal amount; } The PaymentDetails class succinctly encapsulates the necessary attributes for processing payments. This class can be used to pass payment details for processing payments, validating card details, and logging transaction information. Next, implement the PaymentService class, which utilizes MicroProfile Config to inject the necessary configurations. It represents a simple service that could call a payment gateway API using the configurations provided by the custom ConfigSource. import org.eclipse.microprofile.config.inject.ConfigProperty; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; @Path(\"/authorize\") @RequestScoped public class PaymentService { @Inject @ConfigProperty(name = \"payment.gateway.apiKey\") private String apiKey; @Inject @ConfigProperty(name = \"payment.gateway.endpoint\") private String endpoint; @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response processPayment(PaymentDetails paymentDetails) { // Example logic to call the payment gateway API System.out.println(\"Processing payment with details: \" + paymentDetails.toString()); System.out.println(\"Calling payment gateway API at: \" + endpoint); // Assuming a successful payment operation for demonstration purposes // Actual implementation would involve calling the payment gateway and handling the response // Dummy response for successful payment processing String result = \"{\\\"status\\\":\\\"success\\\", \\\"message\\\":\\\"Payment processed successfully.\\\"}\"; return Response.ok(result, MediaType.APPLICATION_JSON).build(); } } Explanation: @Path(\"/authorize\"): Defines the base URI for the RESTful service. This class will handle requests made to URIs that start with /payment/api/authorize. @RequestScoped: Indicates that a new instance of PaymentService is created for each HTTP request. @POST: Specifies that the processPayment method will respond to HTTP POST requests, which is appropriate for operations that change server state (in this case, processing a payment). @Consumes(MediaType.APPLICATION_JSON): Indicates that the method expects requests to have a payload formatted as JSON, aligning with how payment details might be sent. @Produces(MediaType.APPLICATION_JSON): Specifies that the method produces JSON-formatted responses, useful for indicating the result of the payment processing operation. Response processPayment(PaymentDetails paymentDetails): The method now returns a Response object, allowing for more flexible HTTP response handling. The PaymentDetails parameter would be a POJO (Plain Old Java Object) representing the payment information sent by the client. The clients can call to process payments through the e-Commerce application using this RESTful web service endpoint. The actual logic for calling the payment gateway API and handling the response would be implemented within this method, utilizing the injected configuration properties for authentication and endpoint URL. ConfigSources are hierarchical, which means that we can override properties from one ConfigSource with another ConfigSource. For example, we could create a ConfigSource that reads configuration data from a file, and another ConfigSource that reads configuration data from system properties. The system properties would take precedence over the file-based ConfigSource, which would take precedence over the default ConfigSource. Property getProperty(String propertyName) : Returns information about the given property. If the property is not found, this method returns null. Open Liberty requires a server.xml file for server configuration. This file should be located at /src/main/liberty/config/server.xml within your project. To enable MicroProfile Config, you need to include the mpConfig feature in the section. mpConfig-3.1 Managing configurations for different environments is a crucial aspect of modern application development, especially in microservices architectures where applications may run in development, testing, staging, and production environments with varying configurations. MicroProfile Config provides the flexibility to handle environment-specific configurations efficiently. Here’s how to manage configurations for different environments using MicroProfile Config: Use of Profiles: MicroProfile Config does not explicitly define the concept of profiles for managing environment-specific configurations. However, developers can implement a profile-like mechanism using custom ConfigSource implementations or by organizing configuration properties in a way that differentiates them by environment. For instance, you could prefix configuration keys with the environment name: dev.database.url test.database.url prod.database.url Then, you can programmatically or conditionally load configurations based on the active environment. Environment Variables and System Properties: Leveraging environment variables and system properties is a common and effective way to provide environment-specific configurations. MicroProfile Config automatically includes ConfigSources for both system properties and environment variables, allowing for easy overrides of configurations per environment: String databaseUrl = ConfigProvider.getConfig().getValue(\"database.url\", String.class); Custom ConfigSources: For more complex scenarios or to integrate with external configuration management systems (e.g., Consul, Etcd, AWS Parameter Store), you can implement custom ConfigSources. These sources can dynamically load configurations based on the environment, either by connecting to external services or by loading environment-specific files: public class MyEnvironmentConfigSource implements ConfigSource { // Implementation that loads configurations based on the detected environment } Configuration Isolation: It’s essential to isolate configurations for different environments to prevent accidental leaks of sensitive information (e.g., production database credentials). This can be achieved by using: - separate configuration files for each environment, stored securely and only accessible by the application running in that environment. - Utilizing external secrets management tools to store sensitive configurations, with access controlled by the environment. CI/CD Integration: Integrate environment-specific configuration management into your CI/CD pipelines. Ensure that the correct configurations are applied automatically as part of the deployment process for each environment. Although direct support for configuration change events is not provided by MicroProfile Config specification itself, applications can implement their mechanisms or use external libraries to achieve this functionality. To implement dynamic updates in your MicroProfile Config usage, you might need to adopt one of the following approaches: Manual Refresh: Provide a mechanism (e.g., an admin-restricted endpoint) to manually trigger a refresh of the configuration. This approach gives control over when changes are applied but requires manual intervention. Polling: Implement a scheduler that periodically checks certain configuration properties for changes. This approach is straightforward but might introduce latency between the actual change and its detection. Event-driven Updates: If your configuration source supports event notifications (for example, a database trigger or a cloud service event), you can set up listeners that update your application’s configuration in response to these events. Application-level Event Handling: Design your application components to subscribe to a custom event bus or notification system. When a configuration change is detected (via polling or custom ConfigSource), publish an event to this bus, triggering subscribed components to update their configurations. Custom Configuration Source: Develop a custom ConfigSource that includes logic to listen for changes in the underlying configuration store (such as a database, filesystem, or cloud service). This ConfigSource can then notify the application of changes, prompting it to refresh configuration properties. Runtime Extensions: Some MicroProfile runtimes may offer extensions that support dynamic configuration and change event handling. Check the documentation of your runtime environment for such features and best practices for their usage. Framework/Library Support: Use a third-party library or framework that extends MicroProfile Config with change event support. These libraries might offer annotations or listener interfaces to react to configuration changes automatically. External Configuration Management Tools: Utilize configuration management tools or services that offer webhook or messaging functionalities to notify your application of configuration changes. Upon receiving a notification, the application can reload its configuration context. While MicroProfile Config provides the mechanisms to read from dynamic configuration sources, it does not specify a standard way to listen for changes in configuration properties directly within its API as of version 3.1. Applications need to implement their logic or use additional libraries/frameworks to detect changes in configuration sources and react accordingly. However, some implementations of MicroProfile Config might offer extensions or additional functionalities to support configuration change events. For example, an application can poll a configuration source at intervals to detect changes or use a notification system that triggers configuration reloads. Here are some recommended practices for using MicroProfile Config: Graceful Configuration Reloads: Ensure that your application can gracefully handle configuration reloads, especially in critical components that depend on configuration properties for their operation. Minimize Performance Impact: Design your dynamic configuration update mechanism to minimize performance impacts, especially if using polling mechanisms. Secure Configuration Management: When implementing custom solutions for dynamic configuration, pay attention to security aspects, particularly if configurations include sensitive information. Securing sensitive configuration properties is crucial for maintaining the security and integrity of applications. Encrypt Sensitive Configuration Values: Sensitive information, such as passwords, tokens, and API keys, should be encrypted in the configuration source. Decryption can be handled programmatically within the application or through integration with external secrets management systems. Use Environment-Specific Configuration Files: Separate configuration files for different environments (development, testing, production) can help minimize the risk of exposing sensitive data. For instance, development configurations might use placeholder values, whereas production configurations access secrets from a secure vault or environment variables. Leverage External Secrets Management: Integrating with external secrets management tools (like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault) ensures that sensitive configurations are stored securely and accessed dynamically at runtime. These tools provide mechanisms to control access to secrets and often include auditing capabilities. Use Environment Variables for Sensitive Values: Environment variables can be a secure way to provide configuration to applications, especially for containerized or cloud-native applications. This approach leverages the underlying platform’s security model to protect sensitive information. Implement Access Control: Ensure that only authorized personnel have access to configuration files, especially those containing sensitive information. Use file permissions, access control lists (ACLs), or similar mechanisms provided by the operating system or hosting environment. Audit and Monitor Configuration Access: Regularly audit access to configuration files and monitor for unauthorized access attempts. This can help detect potential security breaches and ensure that only authorized changes are made to the configuration. Configuration Validation: Validate configuration data at startup to ensure that it meets the application’s expected format and values. This step can prevent configuration errors and detect tampering or unauthorized changes. Keep Configuration Data Updated: Regularly review and update configuration data to ensure that it reflects the current operational and security needs. Remove unused properties and update secrets periodically to reduce the risk of compromise. Dynamic configuration management is essential for modern applications, providing the flexibility to adapt to changing environments without downtime. Although MicroProfile Config as of version 3.1 does not define a standard for handling configuration change events directly, applications can still achieve this by combining MicroProfile Config with custom logic or additional tools designed for dynamic configuration management. Always consult the documentation of your MicroProfile implementation to learn about supported features and extensions related to dynamic configuration and change events. While the MicroProfile Config specification provides a powerful and flexible framework for configuration management, handling dynamic updates and configuration change events may require additional custom development or the use of external tools. By considering the strategies mentioned above, developers can effectively manage configuration changes, ensuring their microservices remain responsive and resilient in dynamic environments. The MicroProfile Config specification offers a robust and adaptable framework for managing application configurations. By implementing MicroProfile Config, developers can effectively manage configuration changes, ensuring their microservices remain responsive and resilient in dynamic environments. Integrating external configuration providers with MicroProfile Config extends the flexibility and dynamism of configuration management in microservices architectures. By implementing custom ConfigSources, applications can seamlessly adapt to various environments and configuration paradigms, pulling configuration data from virtually any source. Handling missing or invalid configurations in MicroProfile Config involves using default values, optional properties, custom ConfigSource implementations, and appropriate exception handling. By following these practices, you can ensure that your application remains robust and flexible, even in the face of configuration challenges.","title":"MicroProfile Configuration","component":"microprofile-tutorial","version":"6.1","name":"chapter05","url":"/microprofile-tutorial/6.1/chapter05/chapter05.html","titles":[{"text":"Topics to be covered","hash":"topics-to-be-covered","id":1},{"text":"Understanding MicroProfile Configuration","hash":"understanding-microprofile-configuration","id":2},{"text":"Different environments required for Microservices development","hash":"different-environments-required-for-microservices-development","id":3},{"text":"Working with Various Configuration Sources","hash":"working-with-various-configuration-sources","id":4},{"text":"Built-in Configuration Sources","hash":"built-in-configuration-sources","id":5},{"text":"Types of Configuration Sources","hash":"types-of-configuration-sources","id":6},{"text":"Key capabilities of MicroProfile Configuration","hash":"key-capabilities-of-microprofile-configuration","id":7},{"text":"Implementing Configuration Properties","hash":"implementing-configuration-properties","id":8},{"text":"Reading Configuration Properties","hash":"reading-configuration-properties","id":9},{"text":"Specifying Default Values for a ConfigProperty","hash":"specifying-default-values-for-a-configproperty","id":10},{"text":"Type Conversion in ConfigProperty","hash":"type-conversion-in-configproperty","id":11},{"text":"Converting Configuration data to a POJO","hash":"converting-configuration-data-to-a-pojo","id":12},{"text":"Creating a Custom ConfigSource","hash":"creating-a-custom-configsource","id":13},{"text":"Registering a ConfigSource","hash":"registering-a-configsource","id":14},{"text":"Accessing the Configuration Data","hash":"accessing-the-configuration-data","id":15},{"text":"Enabling MicroProfile Config in Open Liberty","hash":"enabling-microprofile-config-in-open-liberty","id":16},{"text":"Managing Configuration for Different Environments","hash":"managing-configuration-for-different-environments","id":17},{"text":"Strategies for Handling Configuration Change Events","hash":"strategies-for-handling-configuration-change-events","id":18},{"text":"Best Practices and Securing Configuration in MicroProfile Config","hash":"best-practices-and-securing-configuration-in-microprofile-config","id":19},{"text":"Summary","hash":"summary","id":20}]},"15":{"id":15,"text":"This chapter provides an in-depth exploration of MicroProfile Health, a critical component for ensuring the reliability and availability of microservices. This specification aims to enhance the observability of microservices in a cloud environment where automatic scaling, failover, and recovery are essential for maintaining service availability and reliability. In this chapter, we will learn about different types of health checks and standard health indicators provided by MicroProfile. Overview of MicroProfile Health Key Concepts Types of Health Checks Exposing Health Checks Steps for Implementing Health Checks Integration with CDI Accessing Health Checks Kubernetes Probe Configuration Best Practices for Effective Health Checks The MicroProfile Health specification offers a standardized mechanism for microservices to report their health status. In the context of microservices, \"health\" refers to the ability of a microservice to perform its functions correctly and efficiently. The health check mechanism is crucial for operating microservices in a cloud or containerized environment where automated processes need to make decisions about whether to restart a failing service, reroute traffic away from an unhealthy service, or take other actions to maintain overall system reliability. Let’s delve into the essentials of MicroProfile Health, its importance, and how it works. At its core, the MicroProfile Health specification defines a mechanism for microservices to report their health status via HTTP. These health checks can be used by external systems to verify the operational status of the services. This is crucial in modern cloud environments where automated processes continuously monitor service health, initiate failover procedures, and manage load balancing to ensure high availability and reliability. A health check is a test that can be used to determine the health of an application or service. This mechanism is implemented via standard HTTP endpoints that respond with the health status of the service. These endpoints are typically exposed at predefined paths, such as /health, /health/live (for liveness), /health/ready (for readiness), and /health/started (for startup). Health status is communicated through a simple JSON format, which can be easily interpreted by humans and machines. Applications servers that support MicroProfile may offer built-in mechanisms or simplified configurations to define such health checks. MicroProfile Health Check defines three main types of health checks, each with its own annotation to indicate the MicroProfile Health runtime about the type of check being performed, allowing it to execute and report health check responses appropriately. These are: Liveness checks help to determine if a microservice is in a state where it can perform its functions correctly. A failing liveness check suggests that the microservice is in a broken state, and the only way to recover might be to restart the microservice. This type of health check is crucial for detecting deadlocks, infinite loops, or any conditions that render the microservice unresponsive or dysfunctional. Liveness checks are annotated with @Liveness. Readiness checks are used to determine if a microservice is ready to process requests. If a readiness check fails, it indicates that the microservice should not receive any inbound requests because it’s not ready to handle them properly. This can be due to the application still initializing, waiting for dependencies, or any other condition that would prevent it from correctly processing incoming requests. Readiness checks are annotated with @Readiness. Startup checks are designed for verifying the microservice’s health immediately after it has started. This type of check is useful for applications that require additional initialization time or need to perform certain actions before they are ready to serve requests. Including startup checks in the health checking mechanism is crucial because if we hit the liveness probe before the application is fully initialized, it could cause a continuous restart loop. Startup checks provide a mechanism to postpone other health checks until certain startup conditions are fulfilled. This ensures that readiness and liveness probes are not prematurely activated, allowing the microservice adequate time to complete its initialization processes, such as loading configurations, establishing database connections, or performing necessary pre-service tasks.These checks are annotated with @Startup. Health checks are exposed via HTTP endpoints automatically without additional configuration needed from the developer’s side. The runtime environment provides these endpoints: /health: Aggregates all health check responses. /health/live: Returns responses from liveness checks. /health/ready: Returns responses from readiness checks. /health/started: Returns responses from startup checks. These endpoints return a JSON object containing the overall status (UP or DOWN) and individual health check responses, including their names, statuses, and optional data. For example a LivenessCheck, if accessed via /health/live, the JSON response might look something like this when the service is healthy: { \"status\": \"UP\", \"checks\": [ { \"name\": \"LivenessCheck\", \"status\": \"UP\" } ] } If the service is unhealthy, the \"status\" field would be \"DOWN\", and additional data might be provided to indicate the cause of the health check failure. Each type of health check is implemented as a procedure annotated with the respective annotation. Each procedure returns a HealthCheckResponse indicating the health status (UP or DOWN) and optionally includes additional details. Implementing these health check types in microservices architecture ensures that services are only used when they are in a healthy state and can correctly process requests. This enhances the overall reliability and maintainability of applications. Applications can implement multiple health checks of each kind. The overall health status reported by the application is a logical AND of all individual health checks. A special endpoint /health aggregates the results from all three types of checks. To implement health checks for microservices using MicroProfile Health, you would generally follow a pattern to define health check procedures that align with the services' operational characteristics. The Health Check API allows us to expose information about the health of our application. This information can be used by load balancers and other tools to determine if an application is healthy. The HealthCheck functional interface uses CDI beans with annotations (@Liveness, @Readiness, and, @Startup) to mark a class as a health checker for liveness, readiness and startup. They are automatically discovered and registered by the runtime. Implementations of this interface are expected to be provided by applications. The Health Check API defines a contract for health check implementations. A health check is a Java class that implements the HealthCheck functional interface: package org.eclipse.microprofile.health; @FunctionalInterface public interface HealthCheck { HealthCheckResponse call(); } You can check out the actual code here - https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheck.java The HealthCheckResponse class is used to represent the result of a health check invocation. It contains information about the health check, such as name, state (up or down), and data that can be used for troubleshooting. The call() method of HealthCheck interface is used to perform the actual health check and return a HealthCheckResponse object: package org.eclipse.microprofile.health; public class HealthCheckResponse { private static final Logger LOGGER = Logger.getLogger(HealthCheckResponse.class.getName()); // the name of the health check. private final String name; // the outcome of the health check private final Status status; // information about the health check. private final Optional> data; // Status enum definition public enum Status { UP, DOWN } // Getters public String getName() { return name; } public Status getStatus() { return status; } public Optional> getData() { return data; } } The provided code snippet offers a conceptual and simplified implementation of the HealthCheckResponse class to illustrate how health check responses can be structured within the MicroProfile Health framework. To view the actual HealthCheckResponse class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponse.java The HealthCheckResponseBuilder abstract class provides a fluent API for constructing instances of HealthCheckResponse. This means you can chain method calls to set various properties of the response in a single statement, improving code readability and maintainability. package org.eclipse.microprofile.health; public abstract class HealthCheckResponseBuilder { // Sets the name of the health check response. public abstract HealthCheckResponseBuilder name(String name) { this.name = name; } // Sets the status of the health check to UP public abstract HealthCheckResponseBuilder up(); // Sets the status of the health check to DOWN public abstract HealthCheckResponseBuilder down(); // Adds additional string data to the health check response public HealthCheckResponseBuilder withData(String key, String value); // Adds additional numeric data to the health check response public HealthCheckResponseBuilder withData(String key, long value); // Sets the status of the health check response public abstract HealthCheckResponseBuilder status(boolean up); // Builds and returns the HealthCheckResponse instance public abstract HealthCheckResponse build(); } The above code snippet offers a conceptual and simplified definition of the HealthCheckResponseBuilder abstract class to illustrate how health check responses can be structured within the MicroProfile Health framework. For the actual HealthCheckResponseBuilder abstract class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponseBuilder.java Below are the steps for implementing Health Checks for each of the microservices: Add MicroProfile Health Dependency: To utilize MicroProfile Health in a Java project, include the MicroProfile Health API dependency in your pom.xml or build.gradle file. For maven, add: org.eclipse.microprofile.health microprofile-health-api 4.0.1 For gradle, add: implementation 'org.eclipse.microprofile.health:microprofile-health-api:4.0.1' When implementing MicroProfile Health checks, including the MicroProfile Health API dependency in your project is not enough. You need an actual implementation on the classpath. This could be a MicroProfile-compatible server runtime such as Open Liberty, Quarkus, Payara Micro, or WildFly. Without an implementation present at runtime, the application will not be able to execute health checks. The health information can be used by other tools to help keep our application running well. Health checks in MicroProfile are implemented as CDI beans that implement the HealthCheck interface. Each health check procedure is a method that returns a HealthCheckResponse. You can define different types of health checks (readiness, liveness, and startup) depending on the type of check by annotating the health check class with @Readiness, @Liveness, or @Startup. These methods return a HealthCheckResponse object, which includes the health check status (UP or DOWN) and additional metadata about the health check. Readiness Check: package io.microprofile.tutorial.store.product.health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Readiness; import io.microprofile.tutorial.store.product.entity.Product; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @Readiness @ApplicationScoped public class ProductServiceHealthCheck implements HealthCheck { @PersistenceContext EntityManager entityManager; @Override public HealthCheckResponse call() { if (isDatabaseConnectionHealthy()) { return HealthCheckResponse.named(\"ProductServiceReadinessCheck\") .up() .build(); } else { return HealthCheckResponse.named(\"ProductServiceReadinessCheck\") .down() .build(); } } private boolean isDatabaseConnectionHealthy(){ try { // Perform a lightweight query to check the database connection entityManager.find(Product.class, 1L); return true; } catch (Exception e) { System.err.println(\"Database connection is not healthy: \" + e.getMessage()); return false; } } } Liveness Check: package io.microprofile.tutorial.store.product.health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.HealthCheckResponseBuilder; import org.eclipse.microprofile.health.Liveness; import jakarta.enterprise.context.ApplicationScoped; @Liveness @ApplicationScoped public class ProductServiceLivenessCheck implements HealthCheck { @Override public HealthCheckResponse call() { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); // Maximum amount of memory the JVM will attempt to use long allocatedMemory = runtime.totalMemory(); // Total memory currently allocated to the JVM long freeMemory = runtime.freeMemory(); // Amount of free memory within the allocated memory long usedMemory = allocatedMemory - freeMemory; // Actual memory used long availableMemory = maxMemory - usedMemory; // Total available memory long threshold = 100 * 1024 * 1024; // threshold: 100MB // Including diagnostic data in the response HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named(\"systemResourcesLiveness\") .withData(\"FreeMemory\", freeMemory) .withData(\"MaxMemory\", maxMemory) .withData(\"AllocatedMemory\", allocatedMemory) .withData(\"UsedMemory\", usedMemory) .withData(\"AvailableMemory\", availableMemory); if (availableMemory > threshold) { // The system is considered live responseBuilder = responseBuilder.up(); } else { // The system is not live. responseBuilder = responseBuilder.down(); } return responseBuilder.build(); } } The above code uses the HealthCheckResponseBuilder to construct the response. Depending on the outcome of checkDatabaseConnection(), the health check response is marked either \"up\" or \"down\", and relevant data is added to the response using .withData(key, value). This approach allows for rich, descriptive health check responses that can convey detailed status information, not just binary up/down states. Startup Check: package io.microprofile.tutorial.store.product.health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import jakarta.ejb.Startup; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.PersistenceUnit; @Startup @ApplicationScoped public class ProductServiceStartupCheck implements HealthCheck{ @PersistenceUnit private EntityManagerFactory emf; @Override public HealthCheckResponse call() { if (emf != null && emf.isOpen()) { return HealthCheckResponse.up(\"ProductServiceStartupCheck\"); } else { return HealthCheckResponse.down(\"ProductServiceStartupCheck\"); } } } The specification also emphasizes the importance of integrating health checks with the application’s Context and Dependency Injection (CDI) context, enabling health check procedures to be automatically discovered and invoked by the runtime. MicroProfile Health thus provides a robust and standardized way to implement health checks, facilitating the management and orchestration of microservices in a cloud environment. Once defined, these health check procedures are automatically discovered and invoked by the MicroProfile Health runtime. They are accessible through standardized HTTP endpoints provided by MicroProfile Health (/health, /health/live, /health/ready, /health/started) and can be used by orchestration tools (like Kubernetes) or monitoring systems to manage and monitor the health of your microservices. This approach allows you to tailor health checks to the operational specifics of each microservice, providing a robust mechanism for observing and managing your application’s health in a cloud-native environment. Integrating MicroProfile Health checks with Kubernetes probes allows you to leverage Kubernetes' native capabilities to manage the lifecycle of your containers based on their current health status. Specifically, you can map Liveness, Readiness, and Startup probes in Kubernetes to the corresponding health check types defined by the MicroProfile Health specification. Here’s a basic overview of how each type of MicroProfile Health check maps to Kubernetes probes: Liveness Probes: Determine if a container is running and healthy. If a liveness probe fails, Kubernetes will kill the container and create a new one based on the restart policy. *Readiness Probes: Determine if a container is ready to serve traffic. If a readiness probe fails, Kubernetes will stop sending traffic to that container until it passes again. Startup Probes: Determine if a container application has started. These are useful for applications that have a long startup time to prevent them from being killed by Kubernetes before they are up and running. To configure these probes in your Kubernetes pod, you can use the livenessProbe, readinessProbe, and startupProbe fields in your container specification. Here’s an example of how you might define a readiness probe in your Kubernetes pod configuration, that utilizes a MicroProfile Health endpoint: apiVersion: v1 kind: Pod metadata: name: mp-pod spec: containers: - name: my-mp-app image: myimage:v1 ports: - containerPort: 8080 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 15 timeoutSeconds: 2 periodSeconds: 5 failureThreshold: 3 In the above example, the readinessProbe is configured to make an HTTP GET request to the /health/ready endpoint, which is the default endpoint provided by MicroProfile Health for readiness checks. Similarly, you can configure livenessProbe and startupProbe by specifying /health/live and /health/startup endpoints respectively. It’s important to adjust the initialDelaySeconds, timeoutSeconds, periodSeconds, and failureThreshold according to the specifics of your application to ensure that Kubernetes accurately reflects the state of your containers based on its health checks. Here are some best practices for implementing and utilizing health checks effectively: Clearly Define Health Check Types: Use readiness, liveness, and startup checks appropriately to reflect the state of your microservices. This helps in accurately signaling the service’s ability to handle traffic and its current operational state. Implement Meaningful Health Checks: Ensure that your health checks meaningfully reflect the operational aspects they are intended to monitor. Avoid trivial checks that do not accurately represent the service’s health. Utilize Health Check Responses: Make effective use of the health check responses, including the UP/DOWN status and additional metadata. This information can be valuable for logging and reporting on the health state of your services. Secure Health Check Endpoints: Consider the security of your health check endpoints, especially if they expose sensitive details about the application’s state. Monitor Health Check Performance: Health checks should be lightweight and not introduce significant overhead. Monitor the performance of your health checks and optimize as needed to prevent impacting the application’s performance. Logging Health Check Results: Implementing logging within your health check procedures can provide insights into the health status over time. Log entries can be made when health check statuses change or when significant health-related events occur. Reporting and Alerting: Based on logged health check results, implement reporting mechanisms to visualize the health over time and set up alerting for when health checks fail. This could be integrated with existing monitoring and alerting tools. By following these best practices, you can effectively implement and expose health checks in your MicroProfile applications, improving observability and reliability, especially in cloud-native environments. This chapter provided a comprehensive overview of MicroProfile Health, emphasizing its critical role in enhancing the observability and reliability of microservices within cloud environments. Key topics included an introduction to the MicroProfile Health specification, detailed explanations of health check types (liveness, readiness, and startup checks), and guidance on implementing, exposing, and effectively utilizing these health checks. The essence of MicroProfile Health lies in its standardized mechanism for microservices to report health status via HTTP endpoints, facilitating automated decision-making processes like scaling, failover, and recovery in cloud or containerized environments. The specification defines three primary types of health checks: liveness, readiness, and startup checks, each designed to assess different aspects of a microservice’s operational status. Implementing health checks involves creating procedures annotated with the respective health check annotations. These procedures return a HealthCheckResponse indicating the service’s health status (UP or DOWN). These checks are automatically exposed via predefined HTTP endpoints, allowing easy integration with orchestration tools like Kubernetes. The chapter also touched on best practices for effective health checks, including defining meaningful checks, utilizing health check responses, handling failures gracefully, and securing health check endpoints. In conclusion, MicroProfile Health offers a robust framework for monitoring and managing the health of microservices, ensuring that services remain reliable and available in dynamic cloud environments. By following the guidelines and best practices outlined in this chapter, developers can effectively implement and leverage health checks to maintain the overall health of their applications.","title":"MicroProfile Health","component":"microprofile-tutorial","version":"6.1","name":"chapter06","url":"/microprofile-tutorial/6.1/chapter06/chapter06.html","titles":[{"text":"Introduction","hash":"introduction","id":1},{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":2},{"text":"Overview of MicroProfile Health","hash":"overview-of-microprofile-health","id":3},{"text":"Key Concepts","hash":"key-concepts","id":4},{"text":"Health Check","hash":"health-check","id":5},{"text":"Types of Health Checks","hash":"types-of-health-checks","id":6},{"text":"Liveness Checks","hash":"liveness-checks","id":7},{"text":"Readiness Checks","hash":"readiness-checks","id":8},{"text":"Startup Checks","hash":"startup-checks","id":9},{"text":"Exposing Health Checks","hash":"exposing-health-checks","id":10},{"text":"Example JSON Response","hash":"example-json-response","id":11},{"text":"Standard Health Check","hash":"standard-health-check","id":12},{"text":"Implementing and Exposing Health Check","hash":"implementing-and-exposing-health-check","id":13},{"text":"The HealthCheck interface","hash":"the-healthcheck-interface","id":14},{"text":"The HealthCheckResponse class","hash":"the-healthcheckresponse-class","id":15},{"text":"The HealthCheckResponseBuilder class","hash":"the-healthcheckresponsebuilder-class","id":16},{"text":"Steps for Implementing Health Checks","hash":"steps-for-implementing-health-checks","id":17},{"text":"Implementing Health Checks","hash":"implementing-health-checks","id":18},{"text":"Integration with CDI","hash":"integration-with-cdi","id":19},{"text":"Accessing Health Checks","hash":"accessing-health-checks","id":20},{"text":"Kubernetes Probe Configuration","hash":"kubernetes-probe-configuration","id":21},{"text":"Best Practices for Effective Health Checks","hash":"best-practices-for-effective-health-checks","id":22},{"text":"Summary","hash":"summary","id":23}]},"16":{"id":16,"text":"This chapter provides a comprehensive and detailed overview of MicroProfile Metrics, a widely used specification for monitoring microservices. You will gain an understanding of the various types of metrics and how you can use them to monitor microservices effectively. Additionally, this chapter covers the standard metrics provided by MicroProfile and how you can leverage them to monitor various aspects of microservices. Furthermore, this chapter discusses the process of instrumenting microservices, which involves adding code to the application to collect metrics. You will learn how to expose endpoints to access metric data and interpret the data generated by these metrics. This chapter also highlights the importance of integrating monitoring solutions with MicroProfile Metrics. You will learn how to incorporate monitoring solutions and choose the right monitoring solution for your needs. By the end of this chapter, you will have a deep understanding of MicroProfile Metrics and the various techniques for monitoring microservices. This chapter will equip you with the knowledge and skills to effectively monitor your microservices and ensure they perform optimally. Introduction to MicroProfile Metrics Need for Metrics in Microservices Types of Metrics MicroProfile Metrics Dependency Metrics Annotations Categories of Metrics Metric Registry Instrumenting Microservices with Metrics Creating Custom Metrics It is essential to monitor your microservices to ensure smooth operations. You can monitor a microservice using two different techniques: Metrics and health checking. Health checks provide information on the health status of a service, such as whether it is up and running, while Metrics offer more detailed information on its performance, such as response times, throughput, and error rates. In the previous chapter, we discussed health checks and their importance. This chapter will cover the MicroProfile Metrics specification, which provides a standardized way of collecting and exposing performance data for Java microservices. MicroProfile Metrics is a specification for developers who want to measure their applications' performance more thoroughly. It provides a set of annotations and APIs to track various metrics related to the application’s health and performance. For instance, developers can use these APIs to track metrics such as the number of requests processed, the response time of each request, and the size of the response sent back to the client. This specification defines a standardized format for exposing metrics, which other tools and frameworks can easily collect and track. By using this specification, developers can monitor the performance of their applications in real time and identify any issues that may impact the user experience. Moreover, this specification defines a set of standard metrics that we can expose in Prometheus. With the help of this tool, developers can optimize their applications for better performance, ensuring that they meet the requirements of their consumers while delivering a seamless experience. Prometheus is a powerful tool designed to monitor and collect metrics from your services. It provides a highly efficient time-series database system that securely stores your data for long-term analysis. With Prometheus, you can easily visualize and gain insights into your system’s performance, allowing you to make informed decisions and optimize your services for better efficiency and reliability. Metrics, enables developers and operators to monitor and measure the behavior of microservices at runtime. This observability is crucial for: Performance Tuning: Identifying bottlenecks and optimizing resource utilization to ensure services are running efficiently. Scalability: Making informed decisions on when to scale services up or down based on real-time data on load and performance. Troubleshooting: Quickly pinpointing issues by analyzing trends in performance metrics, leading to reduced downtime. Service Health Monitoring: Complementing the MicroProfile Health checks by providing deeper insights into the internal state of a service, beyond simple up/down statuses. MicroProfile Metrics offers a range of customizable metrics that can be used to measure and monitor microservices' performance. It allows developing microservices that are observable, manageable, and which provide insights into their behavior. The MicroProfile Metrics specification includes four different types of metrics that serve specific monitoring purposes: Counter, Gauge, Histogram, and Timer. Each of these types offers unique insights into different aspects of application behavior and performance. Below is the breakdown of available metric types: Counter: It is a simple metric type that represents a single numerical value that can only increase over time. This metric is typically used to count occurrences of certain events, such as the number of requests processed, items created, or tasks completed. Monitoring tools like Prometheus are commonly used to analyze changes in the Counter’s value over specific intervals. These tools can track the differences in the Counter’s value across time periods, providing insights into the rate of occurrences and trends. Gauge: It is a metric type that measures an instantaneous value of something , which can arbitrarily go up or down. It’s used to capture the value of a metric at a particular point in time like the size of a queue, memory usage, or current number of active user sessions. Gauges are typically used for values that change over time, providing a current \"gauge\" of the system’s state. Histograms: They provide a distribution of values for a given metric, which are useful for identifying performance outliers. It measures the frequency of values in different ranges (or \"buckets\") and is useful for tracking the distribution of values, such as response times or data sizes. Histograms can give insights into the average, percentiles, and trends of the measured data over time. Timer: It is a specialized metric type that aggregates timing durations and provides data such as the count, total time, mean, and maximum duration. It can also report the duration distribution. Timers are invaluable for tracking the duration of certain activities or operations within your application, such as processing time or method execution time. Note: Counters, Histograms, and Timers, which are updated synchronously when annotations or API calls are made to update them, Gauges are registered as callbacks. These callbacks are invoked to retrieve their value at the moment the list of metrics is requested, typically by a monitoring tool calling the /metrics endpoint. This allows Gauges to provide a real-time snapshot of dynamic values as they fluctuate. By leveraging these metrics, developers and operators can gain a deeper understanding of how their microservices are performing. They can use this information to identify areas where improvements can be made and optimize their microservices' performance. If you’re using Maven, add the following dependency to your pom.xml file located in the root folder of your project: org.eclipse.microprofile.metrics microprofile-metrics-api 5.1.1 For Gradle, add the corresponding dependency to your build.gradle file located within the root folder of your project: dependencies { providedCompile 'org.eclipse.microprofile.metrics:microprofile-metrics-api:5.1.1' } MicroProfile Metrics defines a set of annotations to be used for exposing metrics. These annotations can be used on classes, methods, or fields. Table 7-1 shows the list of Metrics Annotation along with their descriptions. Annotation Description @Timed Times how long a method takes to execute and exposes this information as a metric. @Counted Tracks how many times a method is invoked and exposes this information as a metric. @Gauge Enables you to expose a custom metric that can be any value. It is useful for exposing application-specific metrics. Besides annotations, MicroProfile Metrics also defines a set of programmatic APIs for working with metrics. These APIs can be used to register custom metrics or access existing metrics. In MicroProfile Metrics, metrics are organized into three distinct scopes: Base, Vendor, and Application. This categorization is designed to clearly separate metrics by their origin and relevance, making it easier for developers and operators to monitor and manage the performance of their microservices. Each scope serves a specific purpose and contains a different set of metrics: Base Metrics are common to all applications, such as the number of CPUs or the amount of free memory. These metrics provide essential information about the underlying Java Virtual Machine (JVM) and the core libraries that are common across all MicroProfile applications. Base metrics typically include JVM-specific metrics such as memory usage, CPU load, thread counts, and garbage collection statistics. The intention behind base metrics is to offer a consistent set of low-level metrics that are universally applicable and useful for monitoring the health and performance of the JVM itself, which is the foundation upon which all MicroProfile applications run. Base metrics are exposed under the path /metrics?scope=base. Application Metrics are specific to an application, they are defined by the developers of the MicroProfile applications themselves. These are custom metrics that are specific to the business logic or operational aspects of the application. Developers use annotations or programmatic APIs to create and register these metrics, tailoring them to monitor the performance and behavior of their application’s unique functionalities. Application metrics enable developers to gain insights into the runtime characteristics of their application, such as the number of transactions processed, response times for specific endpoints, or the rate of specific business events. Application metrics are exposed under the path /metrics?scope=application. Vendor Metrics are specific to a particular vendor or technology. These metrics provide insights into the performance and behavior of the runtime’s internal components and extensions. Since different MicroProfile implementations may offer additional features or optimize certain areas differently, vendor metrics can vary widely between runtimes. They allow runtime vendors to expose unique metrics that are relevant to their implementation, offering users the ability to monitor vendor-specific aspects of their applications. Application metrics are exposed under the path /metrics?scope=vendor. Besides the standard scopes above, MicroProfile Metrics also supports custom scopes. You can use custom scopes to group sets of metrics that you frequently expect to view together. Note: In version 5.x, base metrics have become optional. This allows for flexibility in environments where these metrics may not be necessary or where they can be sourced from alternative monitoring tools. The MetricRegistry component acts as a container for storing and managing metrics within an application. It provides a structured way to collect, organize, and access various types of metrics (e.g., counters, gauges, histograms, and timers) for monitoring the behavior and performance of applications. It offers a centralized repository where metrics can be created and retrieved. This allows applications to consistently monitor critical operational and performance statistics. MicroProfile Metrics creates metric registries for each scope: Application Scope (MetricRegistry.Type.APPLICATION): Contains custom metrics that are specific to the application. These are typically the metrics that developers explicitly create and register to monitor application-specific behaviors. Base Scope (MetricRegistry.Type.BASE): Contains metrics that are fundamental and common across all MicroProfile applications. These metrics provide basic information about the underlying JVM and application server. Vendor Scope (MetricRegistry.Type.VENDOR): Contains metrics that are specific to the implementation of the MicroProfile platform being used. These metrics offer insights into vendor-specific features and optimizations. A metric registry is created as per the above scopes to enable the organization of metrics based on their origin and relevance. Instrumenting microservices with MicroProfile Metrics enables developers to gain detailed insights into their application’s operational health and performance. This level of observability is essential for maintaining scalable and resilient microservice architectures in dynamic environments. MicroProfile Metrics also allows you to track a method’s response time as a timed metric. The code example below shows how to use the @Timed annotation to track the response time. import org.eclipse.microprofile.metrics.annotation.Timed; // … public class ProductResource { // … // Expose the response time as a timer metric @Timed(name = \"productLookupTime\", tags = {\"method=getProduct\"}, absolute = true, description = \"Time spent looking up products\") public Product getProduct(@PathParam(\"id\") Long productId) { return productService.getProduct(productId); } // … It will expose a metric called productLookupTime, which will track the amount of time spent in the getProduct() method in seconds. You can visit the following URL https://localhost:/metrics?scope=application (Replace with the actual port where the server is running) to see the response time of this method as below: … # HELP productLookupTime_seconds_max Time spent looking up products # TYPE productLookupTime_seconds_max gauge productLookupTime_seconds_max{method=\"getProduct\",mp_scope=\"application\",} 0.002270643 … MicroProfile Metrics also allows you to track the number of invocations of a method as a counter metric. The code example below shows how to use the @Counted annotation to track the invocation count. import org.eclipse.microprofile.metrics.Metrics; public class ProductResource { // Expose the invocation count as a counter metric @Counted(name = \"productAccessCount\", absolute = true, description = \"Number of times the list of products is requested\") public Response getProducts() { // Method implementation // .... } } In the example above, the @Counted annotation tells MicroProfile Metrics to track the number of invocations of the getProducts() method and expose this metric as a counter. The name, and description of the metric can also be specified. You can visit the following URL https://localhost:/metrics?scope=application (Replace with the actual port where the server is running) to see the number of times this method is called as below: … # HELP productAccessCount_total Number of times the list of products is requested # TYPE productAccessCount_total counter productAccessCount_total{mp_scope=\"application\",} 3.0 … Creating a custom metric to track the number of products in a catalog involves using the MicroProfile Metrics API. This custom metric can be implemented as a gauge, which measures an instantaneous value (in this case, the current number of products in the catalog). import org.eclipse.microprofile.metrics.annotation.Gauge; … @Path(\"/products\") @ApplicationScoped public class ProductResource { // … @GET @Path(\"/count\") @Produces(MediaType.APPLICATION_JSON) @Gauge(name = \"productCatalogSize\", unit = \"none\", description = \"Current number of products in the catalog\") public long getProductCount() { return productCatalogSize; } } The gauge metric productCatalogSize can be accessed through the following endpoint: /metrics?name=io_microprofile_tutorial_store_product_resource_ProductResource_productCatalogSize This custom metric implementation provides a real-time insight into the size of your product catalog, which can be invaluable for monitoring the scale of your service’s data and understanding its behavior over time. Vendors may, by their own implementation, support /metrics?name= to directly retrieve that metric from all scopes. However, the specification itself only illustrates /metrics?scope=&name=. This Chapter delved into the intricacies of MicroProfile Metrics, illuminating its role as a pivotal specification for efficiently monitoring microservices. Now you are equipped with a thorough understanding of diverse metric types and their application for monitoring microservice performance. This chapter highlighted the need for regular microservice monitoring via metrics and health checks, emphasizing metrics for detailed performance insights such as response times and throughput. Through practical examples, this chapter showcases how to instrument microservices with MicroProfile Metrics, leveraging standard metrics, and creating custom metrics to monitor microservices comprehensively.","title":"MicroProfile Metrics","component":"microprofile-tutorial","version":"6.1","name":"chapter07","url":"/microprofile-tutorial/6.1/chapter07/chapter07.html","titles":[{"text":"Introduction","hash":"introduction","id":1},{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":2},{"text":"Introduction to MicroProfile Metrics","hash":"introduction-to-microprofile-metrics","id":3},{"text":"Need for Metrics in Microservices","hash":"need-for-metrics-in-microservices","id":4},{"text":"Types of Metrics","hash":"types-of-metrics","id":5},{"text":"MicroProfile Metrics Dependency","hash":"microprofile-metrics-dependency","id":6},{"text":"Metrics Annotations","hash":"metrics-annotations","id":7},{"text":"Categories of Metrics","hash":"categories-of-metrics","id":8},{"text":"Metric Registry","hash":"metric-registry","id":9},{"text":"Types of Metric Registries","hash":"types-of-metric-registries","id":10},{"text":"Instrumenting Microservices with MicroProfile Metrics","hash":"instrumenting-microservices-with-microprofile-metrics","id":11},{"text":"Tracking response time using @Timed","hash":"tracking-response-time-using-timed","id":12},{"text":"Tracking number of invocations using @Counted","hash":"tracking-number-of-invocations-using-counted","id":13},{"text":"Creating a Custom Metric","hash":"creating-a-custom-metric","id":14},{"text":"Summary","hash":"summary","id":15}]},"17":{"id":17,"text":"In a Microservices architecture, an application consists of multiple smaller, autonomous services. This architecture enhances development flexibility, agility, and scalability but introduces new challenges, particularly in ensuring the application’s reliability and managing failures. Unlike monolithic applications, where defects are localized, a single failure in one microservice can propagate across the entire application, potentially causing widespread outages. Therefore, fault tolerance is critical in a microservices architecture to ensure that failures are seamlessly isolated, managed, and recovered. MicroProfile Fault Tolerance offers strategies for building resilient and reliable microservices, ensuring service continuity and stability even during unexpected failures. This chapter explains how to enhance your microservices' resilience and reliability using MicroProfile Fault Tolerance capabilities and annotations. We will also demonstrate how to implement key strategies such as timeouts, retries, fallbacks, circuit breakers, and bulkheads to handle faults. By the end of the chapter, you will understand how to use these strategies to enhance the resilience of your microservices. What is Fault Tolerance? Key Strategies for Enhancing Fault Tolerance Implementing Retry Policies and Configuration Avoiding and Managing Cascading Failures Configuring Circuit Breaker Using @Asynchronous Annotation Setting Timeouts Implementing Fallback Logic Isolating Resources for Fault Tolerance Fault tolerance is a system’s ability to continue working correctly even in case of unexpected failures. A fault-tolerant system should be able to detect, isolate, and recover from errors without human intervention. It is critical in applications based on modern microservices architectures where individual component failures are inevitable due to network issues, resource limitations, or transient errors. Some of the key strategies for enhancing the fault tolerance of a microservices-based application include: Asynchronous execution allows operations to run in a separate thread. It means the caller does not have to wait for the operation to finish, making the application more responsive. For example, when a user searches for products in the product catalog service, the service can asynchronously fetch product recommendations from an external API while immediately returning the main search results to the user, ensuring a fast and responsive experience. When applied individually or in combination, these strategies form the foundation of a fault-tolerant microservices architecture. The following sections delve deeper into their implementation and best practices. A timeout sets a time limit for operations, preventing indefinite waits and freeing up system resources for other tasks. For instance, a timeout in payment service ensures that the application can recover gracefully if the payment processing is taking too long to respond. A retry allows the system to automatically retry failed operations, particularly useful for handling transient errors like temporary network glitches. You can customize the retry policy with parameters such as the delay between retries and maximum retries. Adding jitter prevents synchronized retries across services. For example, a payment service can retry a failed payment authorization request with an external payment gateway to ensure successful transaction processing. A bulkhead isolates failures in one part of a system from other parts by segregating resources, such as thread pools, connection pools, or memory, among different microservices interactions. For example, in an e-commerce application, the catalog service can implement bulkheads using separate thread pools or connection pools for different upstream dependencies, such as the product database and the pricing service. If the pricing service becomes slow or unresponsive, a bulkhead prevents it from consuming all the resources of the catalog service, ensuring that requests to fetch product details from the database continue to work unaffected. A fallback provides a default response if an operation fails. It ensures the system continues providing a meaningful response instead of completely failing. For example, if the database fails or becomes slow in the product catalog service, the system can fetch cached product data to continue serving user requests for product listings. A circuit breaker stops an application from making too many unsuccessful requests to another system. If the number of failures exceeds a threshold, the circuit breaker will open, causing all subsequent requests to fail immediately. After a configured delay, the circuit breaker will half-open and allow limited requests. If those requests succeed, the circuit breaker will close and let all requests go through. MicroProfile Fault Tolerance 1. MicroProfile Fault Tolerance For example, a circuit breaker can be applied to calls to an external inventory service in the Product Catalog Microservice. If the inventory service starts failing or becomes unresponsive, the circuit breaker will open, preventing repeated requests and reducing load. After a configured delay, the circuit breaker will half-open to test the availability of the inventory service with a few requests. If those succeed, the circuit breaker will close, resuming normal operations. The Fault Tolerance API equips developers with annotations to enhance the resilience of microservices against failures. It integrates seamlessly with the MicroProfile Config API, enabling the dynamic configuration of fault tolerance behaviors without modifying the application code. This section will explore using the Fault Tolerance API to build a robust, fault-tolerant microservice. To use the Fault Tolerance API in your project, include the following dependency in your pom.xml file. Ensure you specify the version (e.g., 4.1.1) compatible with your MicroProfile runtime. org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api 4.1.1 The Fault Tolerance API defines a contract for fault tolerance implementations. The MicroProfile Fault Tolerance annotations provide a declarative way to implement fault-tolerant behavior in Java methods, allowing developers to handle failures gracefully with minimal code changes. Annotation Description @Asynchronous Ensures that the annotated method executes in a separate thread, allowing non-blocking execution. This is useful for improving responsiveness and handling long-running tasks asynchronously. @Retry Specifies that the annotated method should automatically retry on failure. Parameters such as maxRetries, delay, maxDuration, and jitter control retry behavior. Configurations can be externalized using MicroProfile Config. @Timeout Specifies the maximum duration (in milliseconds) the method can execute before being aborted. If the timeout is exceeded, a FaultToleranceException is thrown. @CircuitBreaker Defines a circuit breaker mechanism to prevent repeated calls to a failing method. Includes parameters like failureRatio, delay, and requestVolumeThreshold. @Fallback Specifies alternative logic to execute when the primary method fails. This ensures meaningful responses and graceful degradation. @Bulkhead Limits the number of concurrent method executions to isolate system resources and prevent cascading failures. Retries are a fundamental fault tolerance strategy for managing transient failures such as temporary network outages or intermittent service unavailability. The @Retry annotation in the MicroProfile Fault Tolerance API provides a simple and effective way to implement retry policies. By customizing parameters such as the number of retries, delay between attempts, and conditions for retries, you can ensure your application responds to failures gracefully and minimizes downtime. Below is an example of applying the @Retry annotation in a processPayment method within a PaymentService class of the MicroProfile e-commerce project: package io.microprofile.tutorial.store.payment.service; import org.eclipse.microprofile.faulttolerance.Retry; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.MediaType; public class PaymentService { @Retry( maxRetries = 3, delay = 2000, jitter = 500, retryOn = PaymentProcessingException.class, abortOn = CriticalPaymentException.class ) public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException { System.out.println(\"Processing payment for amount: \" + paymentDetails.getAmount()); // Simulating a transient failure if (Math.random() > 0.7) { throw new PaymentProcessingException(\"Temporary payment processing failure\"); } return Response.ok(\"{\\\"status\\\":\\\"success\\\"}\", MediaType.APPLICATION_JSON).build(); } } To store the necessary payment information, the following PaymentDetails class is used. This class acts as a simple data container for payment-related details. class PaymentDetails { private double amount; public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } } The PaymentProcessingException class represents a recoverable error, which triggers retries when thrown. package io.microprofile.tutorial.store.payment.exception; public class PaymentProcessingException extends Exception { public PaymentProcessingException(String message) { super(message); } } The CriticalPaymentException is considered a non-recoverable failure. If this exception occurs, the retry process is aborted. package io.microprofile.tutorial.store.payment.exception; class CriticalPaymentException extends Exception { public CriticalPaymentException(String message) { super(message); } } In this example, the processPayment method attempts to process a payment. If a transient failure occurs (e.g., PaymentProcessingException), the method retries up to three times (maxRetries = 3), and there is a delay of 2000 milliseconds between retries (delay = 2000), with a random variation of up to 500 milliseconds added to the delay (jitter = 500) to avoid synchronized retries (e.g. thundering herd problem). The retries are attempted only for the exception PaymentProcessingException (retryOn = PaymentProcessingException.class) and are aborted if a CriticalPaymentException is encountered (abortOn = CriticalPaymentException.class). This approach helps maintain application resilience while preventing unnecessary retries that could worsen critical failures. A retry policy specifies the conditions under which an operation should be retried. The key attributes of the @Retry annotation include: Parameter Description maxRetries Specifies the maximum number of retries. delay Sets the time (in milliseconds) to wait between retry attempts. jitter Adds a random variation (in milliseconds) to the delay to avoid synchronized retries. retryOn Defines the exception(s) that should trigger a retry. Defaults to all exceptions if not specified. abortOn Specifies the exception(s) that should not trigger a retry, overriding the default retry behavior. maxDuration Limits the total time (in milliseconds) that retries can be attempted. Retry policies can be externalized using the MicroProfile Config API. This allows you to modify the retry behavior without changing the application code. Here’s how to externalize the configuration: Add the @Retry annotation with minimal attributes: package io.microprofile.tutorial.store.payment.service; import org.eclipse.microprofile.faulttolerance.Retry; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.MediaType; public class PaymentService { @Retry public Response processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException { System.out.println(\"Processing payment for amount: \" + paymentDetails.getAmount()); // Simulating a transient failure if (Math.random() > 0.7) { throw new PaymentProcessingException(\"Temporary payment processing failure\"); } return Response.ok(\"{\\\"status\\\":\\\"success\\\"}\", MediaType.APPLICATION_JSON).build(); } } Define the retry policy in a configuration file (e.g., microprofile-config.properties): io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/maxRetries=3 io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/delay=2000 io.microprofile.tutorial.store.payment.service.PaymentService/processPayment/Retry/jitter=500 In this approach, you gain flexibility to adapt retry policies based on the environment, such as increasing retry attempts in production or reducing delays during testing. Limit Retries: Avoid setting maxRetries too high, as excessive retries can overwhelm the system or cause cascading failures. Use Jitter: Always configure jitter to reduce the risk of synchronized retry attempts by multiple services. Abort Non-Recoverable Errors: Use the abortOn parameter to exclude critical exceptions that retries cannot resolve. Monitor Metrics: Integrate with MicroProfile Metrics to track retry patterns and adjust configurations dynamically based on real-world performance. Combine Strategies: For robust error handling, use retries alongside other fault tolerance mechanisms, such as timeouts and circuit breakers. In a distributed microservices architecture, cascading failures occur when the failure of one service propagates to others, potentially causing widespread system outages. Such failures often result from tightly coupled services, unbounded retries, or resource exhaustion. Tight Coupling: Dependencies between services without sufficient isolation mechanisms. Unbounded Retries: Excessive retries on failing services, overwhelming resources. Resource Contention: Exhaustion of critical resources such as thread pools, memory, or database connections. Lack of Fail-Safe Mechanisms: Missing circuit breakers, bulkheads, or fallback logic. Use circuit breakers to isolate failing services. Apply bulkheads to limit the scope of failures and resource usage. Set timeouts to prevent long-running operations from blocking resources. Design retries with care to avoid overwhelming the system. A circuit breaker is a critical fault tolerance mechanism that protects a system from repeated failures of a dependent service. It stops repeated calls to a failing service, allowing it to recover. Parameter Description failureRatio Specifies the proportion of failed requests required to open the circuit breaker. requestVolumeThreshold The minimum number of requests made in a rolling time window before the failure ratio is evaluated. delay The time (in milliseconds) the circuit breaker remains open before transitioning to the \"half-open\" state. successThreshold The number of consecutive successful test requests required in the \"half-open\" state to close the circuit breaker. failOn Specifies the exception(s) considered failures contributing to the failure ratio. Below is an example of configuring a circuit breaker for a service method using the @CircuitBreaker annotation: @CircuitBreaker( requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000, successThreshold = 2, failOn = RuntimeException.class ) public String getProduct(Long id) { // Logic to call the product details service if (Math.random() > 0.7) { throw new RuntimeException(\"Simulated service failure\"); } return productRepository.findProductById(id); } In the above code, the circuit breaker opens if 50% of requests fail (failureRatio = 0.5) after at least 10 requests (requestVolumeThreshold = 10). It remains open for 5 seconds (delay = 5000) and transitions to the \"half-open\" state to test recovery. Two consecutive successful requests (successThreshold = 2) in the \"half-open\" state close the circuit breaker. Using MicroProfile Config, you can externalize circuit breaker parameters to make them adjustable without code changes as below: Update the @CircuitBreaker annotation: @CircuitBreaker (failOn = RuntimeException.class) public String getProduct(Long id) { // Logic to call the product details service if (Math.random() > 0.7) { throw new RuntimeException(\"Simulated service failure\"); } return productRepository.findProductById(id); } Define the configuration in microprofile-config.properties: io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/requestVolumeThreshold=10 io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/failureRatio=0.5 io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/delay=5000 io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/successThreshold=2 Set Realistic Failure Ratios and Thresholds: Tailor parameters to your services' expected load and failure behavior. Monitor Metrics: Use MicroProfile Metrics to monitor circuit breaker state transitions. Combine with Other Strategies: Use circuit breakers alongside retries and timeouts for a robust fault tolerance setup. The @Asynchronous annotation in MicroProfile Fault Tolerance is used to enable asynchronous execution of methods. It allows operations to run in a separate thread, freeing up the main thread for other tasks. This approach enhances the application’s responsiveness and scalability, particularly in high-concurrency or latency-sensitive scenarios. Improved Responsiveness: The caller does not need to wait for the method execution to complete, allowing the application to remain interactive. Non-Blocking Execution: Long-running operations are offloaded to a separate thread, preventing bottlenecks. Scalability: By decoupling method execution from the calling thread, you can handle higher loads without increasing thread contention. Below is an example of using the @Asynchronous annotation with MicroProfile Fault Tolerance: package io.microprofile.tutorial.store.payment.service; import org.eclipse.microprofile.faulttolerance.Bulkhead; import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.faulttolerance.Asynchronous; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @ApplicationScoped public class PaymentService { private static final int SIMULATED_DELAY_MS = 2000; /** * Processes payments asynchronously * * @return A CompletionStage with the result of the operation. */ @Asynchronous public CompletionStage processPayment() { simulateDelay(); return CompletableFuture.completedFuture(\"Payment processed asynchronously.\"); } /** * Simulates a delay in processing */ private void simulateDelay() { try { Thread.sleep(SIMULATED_DELAY_MS); // Simulating delay } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(\"Error during simulated delay\", e); } } } Timeout values can be externalized using the MicroProfile Config API, allowing flexibility to adjust values without modifying code. Here’s how: * Define the @Timeout annotation without specifying the value: @Timeout public String fetchData() { // Logic } Configure the timeout in microprofile-config.properties: io.microprofile.tutorial.store.payment.service.ProductService/fetchData/Timeout/value=1500 Use CompletableStage or Future: Return types like CompletableStage allow asynchronous methods to integrate seamlessly with other asynchronous workflows. When used with other fault tolerance strategies, @Asynchronous provides a powerful mechanism to handle faults without impacting the system’s responsiveness: Asynchronous with Bulkhead: Isolates resources while maintaining non-blocking execution. Handles concurrent requests efficiently using thread pools. Asynchronous with Circuit Breaker: Prevents system overload during failures by breaking the circuit for failing asynchronous methods. The circuit breaker’s delay allows recovery while new threads are available for other tasks. Timeouts are an essential fault tolerance strategy to prevent long-running operations from consuming resources indefinitely. Slow or unresponsive services can degrade overall system performance and reliability in a microservices architecture. The @Timeout annotation provided by MicroProfile Fault Tolerance allows you to define a maximum duration for a method to complete, ensuring that system resources remain available for other tasks. In distributed systems, slow responses from downstream services can cascade through the system, leading to resource contention and degraded performance. Timeouts allow you to: - Abort operations that exceed acceptable time limits. - Free system resources for other operations. - Trigger alternative strategies, such as fallbacks, to maintain functionality. package io.microprofile.tutorial.store.payment.service; import io.microprofile.tutorial.store.payment.entity.PaymentDetails; import io.microprofile.tutorial.store.payment.exception.PaymentProcessingException; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Timeout; @ApplicationScoped public class PaymentService { private static final int TIMEOUT_MS = 1000; private static final double FAILURE_THRESHOLD = 0.7; @Inject private Logger logger; /** * Processes payments asynchronously with a timeout. * * @param paymentDetails the payment details * @return a CompletionStage with the result of the operation */ @Asynchronous @Timeout(TIMEOUT_MS) public CompletionStage processPayment(PaymentDetails paymentDetails) { return CompletableFuture.supplyAsync(() -> { simulateDelay(); logger.info(\"Processing payment for amount: \" + paymentDetails.getAmount()); if (Math.random() > FAILURE_THRESHOLD) { throw new PaymentProcessingException(\"Temporary payment processing failure\"); } return \"{\\\"status\\\":\\\"success\\\", \\\"message\\\":\\\"Payment processed successfully.\\\"}\"; }).exceptionally(ex -> { logger.warning(\"Payment processing failed: \" + ex.getMessage()); return \"{\\\"status\\\":\\\"failure\\\", \\\"message\\\":\\\"Payment failed due to a temporary issue.\\\"}\"; }); } /** * Simulates a delay in processing. */ private void simulateDelay() { try { Thread.sleep(2000); // Simulating delay } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.severe(\"Error during simulated delay: \" + e.getMessage()); throw new RuntimeException(\"Error during simulated delay\", e); } } } In this example, the @Timeout(1000) annotation specifies that the processPayment method must complete within 1000 milliseconds (1 second). If the execution exceeds this time, a TimeoutException will be thrown, and the process will terminate. @Asynchronous ensures non-blocking execution by making the method run in a separate thread. To explore the benefits of asynchronous programming with MicroProfile Fault Tolerance, the following resources provide valuable insights and real-world examples: Asynchronous Programming with MicroProfile Fault Tolerance (Part 1) Asynchronous Programming with MicroProfile Fault Tolerance (Part 2) These articles explain how asynchronous execution enhances system responsiveness, reduces blocking, and ensures better resource utilization in MicroProfile applications. Align Timeouts with SLAs: Ensure timeout values align with service-level agreements and user expectations. Monitor Performance: Use MicroProfile Metrics to monitor execution times and identify operations requiring optimized timeout values. Combine with Fallbacks: Always pair timeouts with fallback logic to provide a reliable response in case of delays. Avoid Overly Short Timeouts: Overly aggressive timeout settings may cause unnecessary failures, particularly in high-latency environments. Combine Timeout with Asynchronous: Use timeout together with asynchronous to improve responsiveness and prevent blocking the calling thread. This approach ensures better resource utilization and system scalability during long-running operations. Fallbacks provide a default response when an operation fails. They ensure the system continues to function, even if the primary operation cannot complete successfully. The @Fallback annotation in MicroProfile Fault Tolerance allows developers to define fallback logic for a method, ensuring graceful degradation. Fallbacks help to: - Maintain system availability during failures. - Provide a meaningful response to users instead of complete failure. - Improve user experience by minimizing disruptions. import org.eclipse.microprofile.faulttolerance.Fallback; import jakarta.ws.rs.core.Response; public class PaymentService { @Fallback(fallbackMethod = \"fallbackProcessPayment\") public Response processPayment(PaymentDetails paymentDetails) { // Simulate a failure throw new RuntimeException(\"Service Unavailable\"); } public Response fallbackProcessPayment(PaymentDetails paymentDetails) { return Response.ok(\"{\\\"status\\\":\\\"failed\\\", \\\"message\\\":\\\"Payment service is currently unavailable.\\\"}\").build(); } } In this example: - The @Fallback annotation specifies that if the processPayment method fails, the fallbackProcessPayment method will be executed. - The fallback method provides a meaningful response, ensuring the user is informed of the service unavailability. A fallback handler class can implement the FallbackHandler interface, allowing for reusable fallback logic across multiple methods. import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.FallbackHandler; import org.eclipse.microprofile.faulttolerance.ExecutionContext; public class ProductService { @Fallback(FallbackHandlerImpl.class) public Product getProduct(Long id) { // Logic to call the product details service if (Math.random() > 0.7) { throw new RuntimeException(\"Simulated service failure\"); } return productRepository.findProductById(id); } } public class FallbackHandlerImpl implements FallbackHandler { @Override public String handle(ExecutionContext context) { return \"Fallback response for product details.\"; } } Fallback logic can be combined with other fault tolerance mechanisms to create a robust strategy: - Timeout with Fallback: Ensure operations terminate within a specific time and provide a fallback if they fail. Example: import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Timeout; import jakarta.enterprise.context.RequestScoped; import io.microprofile.tutorial.store.product.cache.ProductCache; import io.microprofile.tutorial.store.product.entity.Product; @RequestScoped public class ProductService { @Inject private ProductRepository productRepository; // Access to the database @Inject private ProductCache productCache; // Cache mechanism /** * Retrieves a list of products. If the operation takes longer than 2 seconds, * fallback to cached data. */ @Timeout(2000) // Set timeout to 2 seconds @Fallback(fallbackMethod = \"getProductsFromCache\") // Fallback method public List getProducts() { if (Math.random() > 0.7) { throw new RuntimeException(\"Simulated service failure\"); } // database call return productRepository.findAllProducts(); } /** * Fallback method to retrieve products from the cache. */ public List getProductsFromCache() { System.out.println(\"Fetching products from cache...\"); return productCache.getAll().stream() .map(obj -> (Product) obj) .collect(Collectors.toList()); } } This example demonstrates the use of MicroProfile Fault Tolerance annotations @Timeout and @Fallback to enhance the resilience of the ProductService. When getProducts() method is invoked, the application tries to retrieve product data from the database using productRepository.findAllProducts(). The @Timeout(2000) annotation ensures that this operation completes within 2 seconds. If the query executes successfully within this time, the method returns the product list as expected. However, if the execution time exceeds the timeout limit, a TimeoutException is triggered. Additionally, if an exception occurs within the time limit, the method also fails. To handle such failures gracefully, the @Fallback annotation specifies getProductsFromCache() as an alternative method. When a timeout or exception occurs, the fallback method is invoked, fetching product data from the cache instead of the database. This approach guarantees service availability and ensures a seamless user experience, even in scenarios where the database is slow or temporarily unavailable. For improved scalability and performance, @Asynchronous can be combined with @Timeout and @Fallback. This allows the method to execute in a non-blocking manner, freeing up system resources and enabling parallel processing of multiple requests. By utilizing asynchronous execution, the application can handle high loads efficiently while maintaining fault tolerance. To externalize the @Timeout configuration using MicroProfile Config, you can replace the hardcoded timeout value with a configurable property. This allows us to modify the timeout dynamically without changing the source code. Define a Configurable Property: Use @ConfigProperty to inject the timeout value. // ... @RequestScoped public class ProductService { @Inject private ProductRepository productRepository; // Access to the database @Inject private ProductCache productCache; // Cache mechanism // Inject the timeout value from MicroProfile Config @Inject @ConfigProperty(name = \"product.service.timeout\", defaultValue = \"2000\") private long timeoutValue; // ... Use the Configured Value in @Timeout Annotation: Define a getter method and using it in the annotation. ... /** * Provide the timeout value dynamically using a method reference. */ @Timeout(value = getTimeout()) // Use method reference to fetch dynamic value public long getTimeout() { return timeoutValue; } Define the Configuration Property: Configure the timeout in microprofile-config.properties: io.microprofile.tutorial.store.product.service.ProductService.timeout=3000 This sets the timeout to 3000 milliseconds (3 seconds) instead of the default 2000 making your application more configurable and adaptable without code changes. Keep Fallbacks Lightweight: Ensure fallback logic is simple and reliable, avoiding dependencies on other potentially failing services. Provide Meaningful Responses: The fallback response should maintain a reasonable user experience, even if it cannot replicate full functionality. Monitor Fallback Usage: Use metrics to track the frequency of fallback execution, which can indicate service health and the need for improvements. Plan for Degraded Functionality: Ensure the fallback behavior aligns with business priorities and provides the most critical features. Combining fault tolerance strategies, such as @Timeout, @Fallback, @CircuitBreaker, and @Retry, ensures resilience and efficient resource usage. Externalize configurations with MicroProfile Config for flexibility across environments. Resource isolation is a key principle in building resilient microservices. By isolating resources, you prevent failures in one part of the system from spreading and affecting others. MicroProfile Fault Tolerance provides features like bulkheads to achieve resource isolation and ensure critical components remain functional, even when others fail. In a distributed system, shared resources like thread pools, database connections, and network bandwidth can quickly become bottlenecks if not adequately managed. Resource isolation ensures: - Failures in one service do not deplete resources for other services. - Critical operations remain functional even under load or failure conditions. - Better predictability and control over system behavior. Bulkheads are a common pattern for isolating resources by dividing a system into separate pools or partitions. This ensures that a failure in one area does not impact others. The MicroProfile Fault Tolerance standard provides the @Bulkhead annotation to implement this pattern. MicroProfile supports two types of bulkheads: Semaphore-Style Bulkhead: Limits the number of concurrent requests. Thread Pool-Style Bulkhead: Runs a maximum number of requests on a thread pool to isolate operations. The semaphore-style bulkhead pattern limits the number of concurrent requests that can be processed by a service or method at any given time. Any additional requests are immediately rejected when the specified concurrency limit is reached. This approach prevents resource contention and protects the system from being overwhelmed during high traffic or failure scenarios. package io.microprofile.tutorial.store.payment.service; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.Asynchronous; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.logging.Logger; @ApplicationScoped public class PaymentService { @Inject private Logger logger; @Inject @ConfigProperty(name = \"payment.simulatedDelay\", defaultValue = \"1000\") private int simulatedDelay; @Inject @ConfigProperty(name = \"payment.bulkhead.value\", defaultValue = \"5\") private int bulkheadValue; /** * Processes payment transactions with limited concurrency to prevent * system overload and ensure stability during high traffic. * * The @Bulkhead annotation ensures that only a limited number of * concurrent requests can access this method. * The @Asynchronous annotation enables the use of the thread pool * style bulkhead for non-blocking execution. * * @return A success message indicating the processing status. */ @Asynchronous @Bulkhead(value = bulkheadValue) public CompletionStage processPayment() { logger.info(\"Starting payment processing...\"); simulateDelay(); logger.info(\"Payment processing completed.\"); return CompletableFuture.completedFuture(\"Payment processed asynchronously.\"); } /** * Simulates a delay in processing. */ private void simulateDelay() { try { Thread.sleep(simulatedDelay); // Simulating delay } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.severe(\"Error during simulated delay: \" + e.getMessage()); throw new RuntimeException(\"Error during simulated delay\", e); } } } In this example: - The method allows up to 5 concurrent invocations (value = 5). - Any additional requests are rejected to prevent overload, ensuring system stability. The thread-pool-style bulkhead pattern leverages a thread pool to achieve resource isolation. Incoming requests are placed into a queue when the maximum allowed number of threads are in use. Queued requests are executed as threads become available. This design helps manage resource contention effectively. package io.microprofile.tutorial.store.payment.service; import org.eclipse.microprofile.faulttolerance.Bulkhead; import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.faulttolerance.Asynchronous; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @ApplicationScoped public class PaymentService { private static final Logger logger = LoggerFactory.getLogger(PaymentService.class); /** * Processes payment transactions with limited concurrency using a thread pool * to prevent system overload and ensure stability during high traffic. * * The @Bulkhead annotation ensures that only a limited number of concurrent * requests (5 in this case) can access this method, and the @Asynchronous * annotation allows the use of the thread pool style bulkhead. */ @Bulkhead(value = 5, waitingTaskQueue = 10) @Asynchronous public CompletionStage processPayment() { return CompletableFuture.runAsync(() -> { simulateDelay(); System.out.println(\"Payment processed with limited concurrency.\"); }).thenRun(() -> logger.info(\"Payment processed with limited concurrency.\")); } private void simulateDelay() { try { Thread.sleep(1000); // Simulating a delay } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(\"Error during payment processing simulation\", e); } } } In this example, The method uses up to 5 concurrent threads (value = 5) from a thread pool and a queue of up to 10 tasks (waitingTaskQueue = 10).This configuration prevents failures in one operation from depleting shared resources. Bulkhead resource limits can be externalized using MicroProfile Config to allow runtime adjustments. For example: Annotate the method without specific values: @Asynchronous @Bulkhead public CompletionStage processPayment() { logger.info(\"Starting payment processing...\"); simulatePaymentProcessing(); logger.info(\"Payment processing completed.\"); return CompletableFuture.completedFuture(\"Payment processed successfully with an isolated thread pool.\"); } Define bulkhead parameters in microprofile-config.properties: com.example.Service/dynamicBulkheadOperation/Bulkhead/value=5 com.example.Service/dynamicBulkheadOperation/Bulkhead/waitingTaskQueue=10 Isolate Critical Resources: Use bulkheads for high-priority operations, such as authentication, to ensure they are not impacted by failures elsewhere. Monitor Usage: Track bulkhead metrics using MicroProfile Metrics to identify bottlenecks and adjust limits. Plan for Scaling: Test bulkhead configurations under various load conditions to ensure scalability. Combine with Graceful Degradation: Pair bulkheads with fallbacks to handle rejected requests gracefully. By effectively isolating resources, you can ensure that your microservices remain reliable and resilient, even in the face of unexpected failures or high demand. This approach not only protects critical operations but also improves overall system stability. This chapter explored the MicroProfile Fault Tolerance API and essential fault tolerance strategies: Retries: Automatically reattempt failed operations for transient errors. Timeouts: Define maximum execution times for operations to avoid resource blocking. Circuit Breakers: Prevent repeated calls to failing services and allow graceful recovery. Bulkheads: Limit concurrent operations and isolate resource usage. Fallbacks: Provide meaningful responses during failures. By leveraging these strategies and combining them effectively, you can design resilient microservices that gracefully handle failures, minimize disruptions, and ensure a seamless user experience.","title":"MicroProfile Fault Tolerance","component":"microprofile-tutorial","version":"6.1","name":"chapter08","url":"/microprofile-tutorial/6.1/chapter08/chapter08.html","titles":[{"text":"Topics to be Covered","hash":"topics-to-be-covered","id":1},{"text":"What is Fault Tolerance?","hash":"what-is-fault-tolerance","id":2},{"text":"Key Strategies for Enhancing Fault Tolerance","hash":"key-strategies-for-enhancing-fault-tolerance","id":3},{"text":"Asynchronous Execution","hash":"asynchronous-execution","id":4},{"text":"Timeout","hash":"timeout","id":5},{"text":"Retry","hash":"retry","id":6},{"text":"Bulkhead","hash":"bulkhead","id":7},{"text":"Fallback","hash":"fallback","id":8},{"text":"Circuit Breaker","hash":"circuit-breaker","id":9},{"text":"Fault Tolerance API","hash":"fault-tolerance-api","id":10},{"text":"Adding Dependency for Fault Tolerance API","hash":"adding-dependency-for-fault-tolerance-api","id":11},{"text":"MicroProfile Fault Tolerance Annotations","hash":"microprofile-fault-tolerance-annotations","id":12},{"text":"List of Annotations","hash":"list-of-annotations","id":13},{"text":"Implementing Retry Policies and Configuration","hash":"implementing-retry-policies-and-configuration","id":14},{"text":"Applying @Retry in PaymentService class","hash":"applying-retry-in-paymentservice-class","id":15},{"text":"Defining the PaymentDetails Class","hash":"defining-the-paymentdetails-class","id":16},{"text":"Creating Custom Exception Classes for Handling Failures","hash":"creating-custom-exception-classes-for-handling-failures","id":17},{"text":"Understanding the @Retry Parameters","hash":"understanding-the-retry-parameters","id":18},{"text":"Externalizing Configuration with MicroProfile Config","hash":"externalizing-configuration-with-microprofile-config","id":19},{"text":"Best Practices for Retry Policies","hash":"best-practices-for-retry-policies","id":20},{"text":"Avoiding and Managing Cascading Failures","hash":"avoiding-and-managing-cascading-failures","id":21},{"text":"Causes of Cascading Failures","hash":"causes-of-cascading-failures","id":22},{"text":"Strategies to Prevent Cascading Failures","hash":"strategies-to-prevent-cascading-failures","id":23},{"text":"Configuring Circuit Breaker","hash":"configuring-circuit-breaker","id":24},{"text":"Circuit Breaker Parameters","hash":"circuit-breaker-parameters","id":25},{"text":"Externalizing Circuit Breaker Configuration","hash":"externalizing-circuit-breaker-configuration","id":26},{"text":"Best Practices for Circuit Breaker","hash":"best-practices-for-circuit-breaker","id":27},{"text":"Using @Asynchronous Annotation","hash":"using-asynchronous-annotation","id":28},{"text":"Why Use @Asynchronous?","hash":"why-use-asynchronous","id":29},{"text":"Implementation","hash":"implementation","id":30},{"text":"Externalizing Timeout Configuration","hash":"externalizing-timeout-configuration","id":31},{"text":"Best Practices for Using @Asynchronous","hash":"best-practices-for-using-asynchronous","id":32},{"text":"Asynchronous Execution in Fault Tolerance Strategies","hash":"asynchronous-execution-in-fault-tolerance-strategies","id":33},{"text":"Setting Timeouts","hash":"setting-timeouts","id":34},{"text":"Why Use Timeouts?","hash":"why-use-timeouts","id":35},{"text":"Best Practices for Timeouts","hash":"best-practices-for-timeouts","id":36},{"text":"Implementing Fallbacks","hash":"implementing-fallbacks","id":37},{"text":"Why Use Fallbacks?","hash":"why-use-fallbacks","id":38},{"text":"Using Fallback Handlers","hash":"using-fallback-handlers","id":39},{"text":"Combining Fallbacks with Other Fault Tolerance Strategies","hash":"combining-fallbacks-with-other-fault-tolerance-strategies","id":40},{"text":"Externalizing @Timeout Configuration using MicroProfile Config","hash":"externalizing-timeout-configuration-using-microprofile-config","id":41},{"text":"Best Practices for Fallbacks","hash":"best-practices-for-fallbacks","id":42},{"text":"Combining Fault Tolerance Strategies","hash":"combining-fault-tolerance-strategies","id":43},{"text":"Isolating Resources for Fault Tolerance","hash":"isolating-resources-for-fault-tolerance","id":44},{"text":"Why Resource Isolation Matters","hash":"why-resource-isolation-matters","id":45},{"text":"Using Bulkheads to Isolate Resources","hash":"using-bulkheads-to-isolate-resources","id":46},{"text":"Bulkhead Types","hash":"bulkhead-types","id":47},{"text":"Semaphore-Style Bulkhead","hash":"semaphore-style-bulkhead","id":48},{"text":"Thread Pool-Style Bulkhead","hash":"thread-pool-style-bulkhead","id":49},{"text":"Externalizing Bulkhead Configuration","hash":"externalizing-bulkhead-configuration","id":50},{"text":"Best Practices for Resource Isolation","hash":"best-practices-for-resource-isolation","id":51},{"text":"Summary","hash":"summary","id":52}]},"18":{"id":18,"text":"Microservices-based applications have better scalability, flexibility, and resilience, but they suffer from additional challenges regarding availability and performance monitoring. This makes observability critical to ensure these distributed systems operate reliably. MicroProfile Telemetry specification provides a set of vendor-neutral APIs for instrumenting, collecting, and exporting telemetry data such as traces, metrics, and logs. It is built on the foundation of OpenTelemetry from the Cloud Native Computing Foundation (CNCF) project, an open-source observability framework. In this chapter, we will explore the fundamentals of MicroProfile Telemetry, covering topics such as tracing concepts, instrumenting Telemetry, setting up tracing providers, context propagation and correlation, analyzing traces, security considerations for tracing, and more. By the end of this chapter, you will learn how to effectively leverage distributed tracing for debugging, performance monitoring, and system optimization. Introduction to MicroProfile Telemetry Tracing Concepts Spans Traces Context Propagation Correlation Instrumenting OpenTelemetry Tools for Trace Analysis Exporting the Traces Types of Telemetry Agent Instrumentation Analyzing Traces Security Considerations for Tracing MicroProfile Telemetry addresses the operational challenges inherent in modern microservices architectures. Without proper observability, debugging, performance monitoring, and ensuring system reliability become complex and time-consuming. Some of the key challenges in microservices-based applications include: Complexity due to Distributed Architecture: Microservices are often deployed across multiple nodes, containers, or cloud environments, making it challenging to track requests as they move through the system. This lack of visibility increases debugging complexity, making it harder to identify bottlenecks and analyze system behavior. Polyglot Architecture: Microservices are developed using multiple programming languages (e.g., Java, Python, and Go) and frameworks, resulting in inconsistent telemetry data and a lack of standardization in observability. This fragmentation makes correlating logs, traces, and metrics across services difficult. Latency: Communication between Microservices involves latency, and all of this adds up as requests traverse several services. This makes it difficult to identify the root causes of issues. Ensuring High Availability: Failures in one microservice can affect the entire system, impacting multiple dependent microservices. This can lead to downtime or degraded performance, resulting in lost revenue and diminished user trust. To address these challenges, MicroProfile Telemetry specification provides a standardized set of APIs for capturing telemetry data, including trace information and context propagation, to improve observability in distributed systems. By enabling seamless tracing, developers can analyze system behavior, troubleshoot service interactions, and ensure application reliability. MicroProfile Telemetry is vendor-neutral. It allows developers to switch between different OpenTelemetry implementations without modifying their application code. This flexibility ensures that MicroProfile applications can easily integrate with various observability platforms, making it easier to adopt, scale, and maintain Telemetry in modern cloud-native environments. Tracing is critical for observability. It allows developers to inspect the flow of requests as they traverse through distributed systems. Tracing provides visibility into the interactions and dependencies within a system by breaking down a request into multiple spans, and connecting them into traces with context propagated across services. A span is the basic unit of work in tracing. It represents a single operation or task a service performs, such as an HTTP request, a database query, or a computation. Each span contains metadata, including: Operation Name: Describes the activity (e.g., HTTP GET /products). Start Time and Duration: Captures when the operation started and how long it took. Attributes: Key-value pairs providing context (e.g., user IDs, resource names, HTTP status codes). Parent Span ID: Indicates the parent span, forming a relationship within a trace. Spans may also include additional data like logs and events, which help provide a detailed view of the operation’s lifecycle. Spans are connected to form a trace, which helps identify bottlenecks and performance issues. A trace is a collection of related spans representing the end-to-end execution of a request or transaction. It provides a holistic view of how a single request flows through the system, including service interactions. Traces often form a tree structure, where the root span represents the entry point (e.g., a user request), and child spans represent subsequent operations. For example: API Gateway (Root Span) + │ ├── Order Service (Child Span) + │ │ │ ├── Database Query (Another Child Span) + │ │ ├── Fetch Order Details + │ │ ├── Process Order Data + │ │ └── Return Data to Order Service + │ │ │ └── Return Response to API Gateway + │ └── API Gateway Sends Final Response to User Context propagation refers to the mechanism of carrying trace-related metadata, such as trace IDs and span IDs, across service and thread boundaries. This ensures that all spans created during a request can be linked together to form a complete trace. Context propagation is vital for connecting distributed spans and understanding their relationship ensuring trace metadata remains correlated as it travels with requests across service boundaries. Correlation is the process of associating related spans and traces across multiple services and threads to form a cohesive view of a transaction. Correlation enables developers to: Identify the source of bottlenecks or errors in distributed systems. Understand the dependencies and interactions between services. When viewing logs, the traceId and spanId allow you to link specific log entries to the corresponding spans in your tracing system. Trace ID: A unique identifier shared across all spans in a single trace. Span ID: A unique identifier for a single span. It is linked to a parent span, forming a hierarchy. Together, these concepts form the foundation of distributed tracing, enabling developers to monitor, analyze, and optimize the performance of their microservices effectively. MicroProfile Telemetry simplifies instrumentation by integrating OpenTelemetry for distributed tracing. The following steps outline how to instrument telemetry in a MicroProfile E-Commerce application. To enable tracing and exporting of telemetry data, include the MicroProfile Telemetry API dependency in your pom.xml file. org.eclipse.microprofile.telemetry microprofile-telemetry-api 1.1 provided MicroProfile automatically traces requests, but you can manually instrument your code using OpenTelementry APIs. A Tracer is a core component of OpenTelemetry, responsible for creating spans and managing trace data within the application. To use it, inject a Tracer instance into your MicroProfile service: import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.Span; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped public class PaymentService { @Inject Tracer tracer; public void processPayment(String orderId, double amount) { // Create a custom span for tracing the payment process Span span = tracer.spanBuilder(\"payment.process\").startSpan(); try { span.setAttribute(\"order.id\", orderId); span.setAttribute(\"payment.amount\", amount); span.setAttribute(\"payment.status\", \"IN_PROGRESS\"); // Business logic for processing the payment executePayment(orderId, amount); span.setAttribute(\"payment.status\", \"SUCCESS\"); } catch (Exception e) { span.setAttribute(\"payment.status\", \"FAILED\"); span.recordException(e); } finally { span.end(); } } private void executePayment(String orderId, double amount) { System.out.println(\"Processing payment for Order ID: \" + orderId + \", Amount: \" + amount); } } The implementation injects a Tracer, which enables manual span creation and precise trace management within the application. By creating a custom span (payment.process), it captures detailed telemetry data related to the payment process. Additionally, custom attributes such as order.id, payment.amount, and payment.status are attached to the span, providing valuable metadata for trace analysis. The implementation also includes exception handling, ensuring that any failures encountered during payment processing are properly recorded in the trace. Finally, the span is explicitly ended, marking the completion of tracing for this method. This setup ensures that each payment transaction is fully traceable, allowing developers to monitor execution flow, debug issues, and optimize application performance effectively. Use the Tracer to create a span that represents a specific operation or activity in your application: Span span = tracer.spanBuilder(\"my-span\").startSpan(); The method spanBuilder(\"my-span\") creates a new named span, which represents a specific operation within the application’s execution flow. This helps in tracing and monitoring the operation as part of a distributed system. Calling startSpan() marks the beginning of the span lifecycle, ensuring that the span is actively recorded until it is explicitly ended. This allows telemetry data to be captured for performance analysis, debugging, and observability. Attributes enhance trace context by attaching key-value pairs to a span, providing additional metadata that helps filter and analyze traces in observability tools. This helps in contextualizing the trace data: span.setAttribute(\"http.method\", \"GET\"); span.setAttribute(\"http.url\", \"/products/12345\"); span.setAttribute(\"user.id\", \"98765\"); The above statements allow the tracing system to capture essential details about an HTTP request. When the operation completes, end the span to capture the telemetry data: Span span = tracer.spanBuilder(\"payment.process\").startSpan(); try { // Business logic execution } catch (Exception e) { span.recordException(e); span.setAttribute(\"error\", true); } finally { span.end(); } The following tools are commonly used for trace collection, visualization, and analysis in MicroProfile applications: The OpenTelemetry Collector is an open-source telemetry processing system that acts as an intermediary between instrumented applications and observability backends such as Jaeger, Zipkin, and Prometheus. It is designed to receive, process, and export tracing data, making it a powerful tool for managing distributed traces in MicroProfile applications. It is vendor-agnostic, which allows for seamless integration with multiple tracing backends without requiring any changes to application instrumentation. It supports multiple data formats, enabling the ingestion of traces through several protocols, ensuring compatibility across different telemetry sources. Additionally, it offers processing pipelines that let developers filter, batch, and transform trace data before exporting it, optimizing observability workflows. Designed for scalability, the OpenTelemetry Collector can be deployed as a standalone instance or distributed across multiple nodes, making it suitable for both small-scale applications and large enterprise-grade distributed systems. Jaeger is an open-source distributed tracing system developed by Uber, widely used for monitoring microservices and visualizing request flows in cloud-native applications. It provides a powerful visualization interface that enables developers to inspect traces, analyze dependencies between services, and examine execution timelines, making it an essential tool for debugging performance bottlenecks. One of Jaeger’s key capabilities is service dependency analysis, which helps identify how microservices interact, providing insights into latency, failures, and request propagation. It also supports adaptive sampling strategies, allowing developers to control the volume of traces collected to optimize performance without overwhelming storage and processing resources. Additionally, Jaeger offers built-in storage options, allowing trace data to be stored in Elasticsearch, Cassandra, or Kafka, making it scalable and flexible for various deployment environments. Zipkin is a distributed tracing system designed to help developers visualize and diagnose latency issues in microservices-based applications. It provides a lightweight and fast tracing solution, making it ideal for quick deployment with minimal resource usage. Its simplicity and efficiency make it a popular choice for teams looking to implement tracing without significant infrastructure overhead. One of Zipkin’s core strengths is its tag-based searching, which allows developers to filter traces based on metadata such as service name, request ID, or other custom attributes, enabling quick identification of relevant traces. It also offers dependency graph visualization, helping to uncover bottlenecks and inefficiencies in microservices interactions. To accommodate different storage needs, Zipkin supports multiple storage backends, including Elasticsearch, MySQL, and Cassandra, providing flexibility for various deployment scenarios. Grafana Tempo is a distributed tracing backend. Unlike Jaeger and Zipkin, Tempo does not require indexing as it only requires object storage, making it highly scalable and cost-efficient for handling large volumes of trace data. This unique approach allows Tempo to store traces efficiently without increasing storage and query overhead, making it an ideal choice for high-performance microservices environments. One of Tempo’s key advantages is its tight integration with Grafana dashboards, enabling developers to correlate logs, metrics, and traces within a unified observability platform. Additionally, Tempo offers multi-backend support, meaning it can ingest and process trace data from OpenTelemetry, Jaeger, and Zipkin sources, ensuring compatibility with existing tracing setups. Its scalability makes it well-suited for large-scale microservices architectures, where efficiently managing distributed tracing data is crucial. To export the traces we need to configure the exporter type and endpoint in the src/main/resources/META-INF/microprofile-config.properties. For using OTLP (OpenTelemetry Protocol) export, you need to add the following configuration in: # Enable OpenTelemetry otel.traces.exporter=otlp # Set the OTLP exporter endpoint otel.exporter.otlp.endpoint=http://localhost:4317 # Define the service name otel.service.name=payment-service # Sampling rate: (1.0 = always, 0.5 = 50%, 0.0 = never) otel.traces.sampler=parentbased_always_on This sends traces directly to a observability tool, enabling real-time distributed tracing and performance monitoring. To ensure proper tracing, your observability tool (for e.g. Jaeger) must be running to receive trace data. Using OTLP is advantageous because it is the native standard for OpenTelemetry, ensuring seamless integration with a wide range of observability tools. One of its key benefits is that it allows developers to use multiple observability platforms without changing instrumentation, providing a unified and vendor-neutral tracing solution. Once tracing is enabled and the appropriate exporter is configured, the next step is to verify that traces are being captured and sent to the observability backend. This ensures that the MicroProfile Telemetry setup is functioning correctly and that distributed tracing data is available for monitoring and debugging. The simplest way to run Jaeger is with Docker using the command as below: docker run -d --name jaeger \\ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \\ -p 5775:5775/udp \\ -p 6831:6831/udp \\ -p 6832:6832/udp \\ -p 5778:5778 \\ -p 16686:16686 \\ -p 14268:14268 \\ -p 14250:14250 \\ -p 9411:9411 \\ jaegertracing/all-in-one:latest The above command runs the all-in-one Jaeger container, which includes the agent, collector, query service, and UI. The Jaeger UI can be accessed at: https://:16686. Ensure all the services of our MicroProfile E-commerce applications are running. Search using parameters like operation name, time range, or service for the traces associated with different microservices and confirm that the telemetry data is visible. View a detailed breakdown of each span within the trace, including timing and attributes. MicroProfile Telemetry supports multiple approaches to instrumentation and tracing, ensuring flexibility for developers based on their observability needs. The three primary types of telemetry in MicroProfile Telemetry are: Automatic Instrumentation enables distributed tracing without requiring any modifications to the application code. This is particularly beneficial for Jakarta RESTful Web Services and MicroProfile REST Clients, as it enables seamless integration into distributed tracing systems following the semantic conventions of OpenTelemetry. This ensures compatibility across different tracing tools. For example, in the ProductService, which exposes a RESTful endpoint, automatic instrumentation ensures that incoming and outgoing HTTP requests are traced with minimal configuration, without requiring any additional code changes. By default, MicroProfile Telemetry tracing is disabled. To activate it, set the following property in microprofile-config.properties: otel.sdk.disabled=false This ensures that OpenTelemetry’s tracing capabilities are enabled for the application. Manual Instrumentation provides developers with fine-grained control over how telemetry data is collected and structured within a MicroProfile application. By explicitly defining spans, attributes, and trace propagation, developers can gain greater insight into application behavior beyond what automatic instrumentation provides. The @WithSpan annotation provides a simple way to create custom spans within a trace. By annotating a method with @WithSpan, a new span is automatically generated whenever the method is invoked. This span is linked to the current trace context, allowing developers to track key operations without manually managing span lifecycle. import io.opentelemetry.instrumentation.annotations.WithSpan; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class PaymentService { @WithSpan public void processPayment(String orderId) { // Business logic here } } Every time processPayment is called, a new span is created. The span is automatically linked to the current trace context. No need for explicit span creation or lifecycle management. You can use @WithSpan for tracing key business operations, such as order processing, payment handling, or API requests. For greater flexibility, developers can manually create spans using the OpenTelemetry API. The SpanBuilder class provides the ability to define custom span names, making trace analysis more meaningful and structured. Additionally, developers can attach custom attributes to spans, enriching trace data with relevant metadata for deeper insights. This method also offers explicit control over the span lifecycle, allowing spans to be started and ended manually, ensuring they accurately represent specific business operations or execution flows within the application. import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.Span; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @Path(\"/trace\") public class TraceResource { @Inject Tracer tracer; @GET @Path(\"/custom\") public String customTrace() { Span span = tracer.spanBuilder(\"custom-span\").startSpan(); span.setAttribute(\"custom.key\", \"customValue\"); span.end(); return \"Trace recorded\"; } } The method tracer.spanBuilder(\"custom-span\").startSpan() creates a span with a specific name allowing developers to define meaningful trace segments for better observability. Using span.setAttribute(\"custom.key\", \"customValue\"), custom metadata can be attached to the span, enriching trace data with relevant contextual information. Finally, calling span.end() explicitly marks the completion of the span, ensuring accurate tracking of execution duration. The SpanBuilder approach is particularly useful when developers require fine-grained control over when spans start and end, as well as the ability to include detailed metadata for enhanced trace analysis. To manually instrument the processPayment method in the PaymentService, we use OpenTelemetry’s API to create a custom span, add attributes, and control the span lifecycle. import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped public class PaymentService { @Inject Tracer tracer; public void processPayment(String orderId, double amount, String paymentMethod) { // Create a custom span for tracing the payment process Span span = tracer.spanBuilder(\"payment.process\").startSpan(); try { // Add attributes to enrich the trace span.setAttribute(\"order.id\", orderId); span.setAttribute(\"payment.amount\", amount); span.setAttribute(\"payment.method\", paymentMethod); span.setAttribute(\"payment.status\", \"IN_PROGRESS\"); // Business logic for processing the payment System.out.println(“Processing Payment…); // Update span attribute on successful completion span.setAttribute(\"payment.status\", \"SUCCESS\"); } catch (Exception e) { // Capture error in tracing span.setAttribute(\"payment.status\", \"FAILED\"); span.recordException(e); } finally { // End the span to complete the tracing span.end(); } } } The payment.process span is manually created using tracer.spanBuilder(), allowing explicit control over the tracing of the payment process. To enhance trace visibility, custom attributes such as the order ID, payment amount, and payment method are attached to the span, providing valuable context for analysis. Additionally, the payment status is recorded as IN_PROGRESS when processing starts and updated to SUCCESS or FAILED based on the outcome. In the event of an error, the span captures and records the exception, ensuring failure details are logged for debugging. The span lifecycle is carefully managed, starting before the business logic executes and ending only after the process is completed in the finally block. This structured approach guarantees accurate performance monitoring and trace completeness, improving visibility into how payments are processed in a distributed system. Agent Instrumentation enables telemetry data collection without modifying application code by attaching a Java agent at runtime. This approach is particularly useful for legacy applications or scenarios where modifying source code is not feasible. The OpenTelemetry Java Agent dynamically instruments applications, automatically detecting and tracing interactions within commonly used frameworks such as Jakarta RESTful Web Services, database connections, and messaging systems. One of the key advantages of agent-based instrumentation is that it requires no changes to the application’s source code and eliminates the need for recompilation or redeployment. Instead, it can be activated by attaching the agent at application startup. Refer to the OpenTelemetry Java Agent Getting Started page for step-by-step instructions on enabling it for your application. Once enabled, the agent automatically instruments the application, seamlessly integrating with distributed tracing systems without requiring developer intervention. This makes it an efficient and non-intrusive way to implement observability in MicroProfile applications. Once enabled, the agent automatically instruments the application, seamlessly integrating with distributed tracing systems without requiring developer intervention. This makes it an efficient and non-intrusive way to implement observability in MicroProfile applications. Once trace data is collected and exported to a backend system, analyzing these traces becomes a crucial step in understanding the behavior of your distributed microservices architecture. By examining traces, you can gain insights into system performance, identify bottlenecks, and detect failures or anomalies. Tracing backends like Jaeger, Zipkin, or Graphana Tempo provide visual interfaces to explore and analyze traces. These tools display traces as timelines or dependency graphs, making it easier to: Understand the sequence of operations. Identify the services and components involved in a request. Observe how requests propagate through the system. Traces highlight spans with long durations or repeated retries, which often point to bottlenecks or inefficiencies. Pay close attention to: Critical Path: The longest path in a trace that determines the total response time. Service Dependencies: Examine how upstream and downstream services interact to find slow components. Retries and Failures: Repeated spans or high failure rates indicate problematic dependencies or transient errors. Traces provide valuable information for diagnosing failures, including: Error Codes: Look for spans with error attributes, such as http.status_code=500. Exception Details: Many tracing systems capture stack traces or error messages in spans. Service Impact: Identify which upstream and downstream services are affected by the failure. Dependency graphs generated from traces show the interactions between services. These graphs help: Visualize which services depend on each other. Detects circular dependencies or excessive coupling. Plan optimizations by focusing on critical services. Traces, when combined with logs and metrics, provide a comprehensive picture of the system: Logs: Use trace IDs and span IDs in logs to correlate application logs with specific spans. Metrics: Correlate trace performance data with system metrics like CPU usage, memory consumption, or request rates. Example: If a span indicates high latency, check corresponding logs and metrics to identify the underlying cause, such as a resource constraint or network delay. Establish Baselines: Use traces to establish performance baselines for services. Monitor Critical Paths: Focus on traces that traverse critical services or user-facing operations. Use Sampling Strategically: Balance trace volume and storage costs by sampling traces intelligently. Automate Alerts: Set up alerts for abnormal patterns in traces, such as increased latency or failure rates. Collaborate Across Teams: Share trace insights with development, operations, and QA teams to improve system reliability. By analyzing traces effectively, you can identify opportunities to optimize your microservices, ensure smoother operations, and enhance the overall user experience. Tracing tools provide a powerful way to visualize and understand the intricate dynamics of distributed systems. When analyzing traces, developers should look for the following: Long spans: Spans that take a long time to complete may indicate a performance issue. Missing spans: Missing spans can make it difficult to understand the flow of a request. Errors: Errors can indicate problems with a service or a request. High latency: High latency can indicate a problem with the network or a service. By analyzing traces, developers can identify and troubleshoot problems with their microservices applications. This can help developers improve the performance and reliability of their applications. Here are some tips for analyzing traces: Use a trace viewer: A trace viewer is a tool that can help you visualize and analyze traces. Look for patterns: Look for patterns in the traces that may indicate a problem. Correlate traces with metrics: Correlate traces with metrics to get a better understanding of the performance of your application. Use sampling: Use sampling to reduce the number of traces that are collected. This can improve the performance of your tracing system. By following these tips, developers can effectively analyze traces to improve the performance and reliability of their microservices applications. When implementing tracing in your applications, it is crucial to be mindful of security implications. Tracing involves collecting and storing data about application behavior, which can potentially expose sensitive information if not handled properly. Data Sensitivity: Be cautious about the data included in traces. Avoid logging sensitive information such as passwords, API keys, or personally identifiable information (PII). Access Control: Implement strict access controls to limit who can view and manage trace data. Encryption: Consider encrypting trace data at rest and in transit to protect it from unauthorized access. Storage: Carefully manage the storage of trace data. Avoid storing traces indefinitely and implement data retention policies. Third-Party Services: If using third-party tracing services, ensure they have robust security measures in place to protect your data. Traces often include attributes and metadata that can contain sensitive information. Avoid storing or transmitting sensitive details, such as: Personally Identifiable Information (PII) (e.g., names, addresses, social security numbers). Payment information (e.g., credit card numbers). Authentication credentials (e.g., passwords, API keys, tokens). Best Practice: Sanitize attributes before adding them to spans: span.setAttribute(\"user.id\", \"anonymized-user-id\"); span.setAttribute(\"credit.card.last4\", \"****1234\"); To prevent unauthorized access during transmission, ensure that telemetry data is encrypted. Use secure protocols such as HTTPS or TLS for exporting trace data to a backend. *Example:* Configure the tracing provider to use encrypted connections: otel.exporter.jaeger.endpoint=https://secure-jaeger-collector.example.com otel.exporter.otlp.endpoint=https://secure-collector.example.com Trace data can grow rapidly in distributed systems. Retaining it indefinitely increases the risk of exposing sensitive information. Implement retention policies to: Retain traces only for the necessary duration for debugging or performance analysis. Periodically purge older traces from storage. Restrict access to trace data to authorized personnel only. Ensure that your tracing backend implements robust authentication and authorization mechanisms. Best Practice: Use role-based access control (RBAC) to define permissions for viewing and managing traces. Audit access to trace data regularly to identify potential misuse or breaches. Sampling reduces the volume of traces collected and limits the exposure of sensitive data by capturing only a subset of requests. Common strategies include: Random Sampling: Captures a fixed percentage of traces. Rate-Limiting Sampling: Limits the number of traces per second. Key-Based Sampling: Samples traces based on specific attributes (e.g., user ID). Example: Random sampling to limiting the amount of trace data collected: otel.traces.sampler=traceidratio otel.traces.sampler.traceidratio=0.1 Ensure that your tracing practices comply with data protection and privacy regulations such as GDPR, CCPA, or HIPAA. Key considerations include: Anonymizing sensitive data before tracing. Informing users about telemetry collection in your privacy policy. Providing mechanisms to opt out of tracing where required. The tracing infrastructure, such as Jaeger or OpenTelemetry Collector, should be isolated from the public internet and accessible only within secure networks. Best Practice: Deploy tracing backends in private subnets or behind firewalls. Use VPNs or dedicated connections for remote access to tracing dashboards. Tracing can help detect potential security incidents. Monitor traces for unusual patterns, such as: Unexpected spikes in requests. Requests from unknown or unauthorized sources. Abnormal response times indicating possible exploits. Set up alerts for these anomalies to investigate and mitigate potential issues. By following these security considerations, you can leverage the benefits of distributed tracing without compromising the security of your system or the privacy of your users. Careful handling of trace data, coupled with robust encryption, access controls, and compliance practices, ensures that tracing remains a valuable yet secure component of your observability strategy. MicroProfile Telemetry provides a robust foundation for observability in Java-based microservices, enabling developers to implement distributed tracing seamlessly. By leveraging this specification, you can gain deep insights into the flow of requests, identify bottlenecks, and enhance the reliability and performance of your applications. The integration of standardized tracing concepts like spans, traces, and context propagation ensures that developers can maintain a cohesive understanding of their system’s behavior across service boundaries. Through instrumentation, context propagation, and effective trace analysis, MicroProfile Telemetry simplifies the complexities of monitoring and debugging distributed systems. It empowers teams to proactively address issues, optimize performance, and improve the user experience. Moreover, by adhering to security best practices, developers can ensure that telemetry data is protected, compliant with regulations, and free of sensitive information. In this chapter, we explored the critical security considerations surrounding tracing within the MicroProfile Telemetry framework. We emphasized the importance of safeguarding sensitive data by avoiding the inclusion of Personally Identifiable Information (PII) in trace spans. Additionally, we discussed the potential security risks associated with tracing in production environments and the significance of carefully managing sampling rates and data retention policies. By adhering to these security best practices, developers can harness the power of tracing for observability while ensuring the confidentiality and integrity of their applications. As microservices architectures continue to evolve, the ability to observe and trace system interactions will remain a critical factor in maintaining resilient and efficient applications. MicroProfile Telemetry stands as a valuable tool in achieving these goals, providing developers with the observability they need to deliver reliable, high-performance microservices in modern cloud-native environments.","title":"MicroProfile Telemetry","component":"microprofile-tutorial","version":"6.1","name":"index","url":"/microprofile-tutorial/6.1/chapter09/index.html","titles":[{"text":"Topics to be covered","hash":"topics-to-be-covered","id":1},{"text":"Introduction to MicroProfile Telemetry","hash":"introduction-to-microprofile-telemetry","id":2},{"text":"Tracing Concepts","hash":"tracing-concepts","id":3},{"text":"Spans","hash":"spans","id":4},{"text":"Traces","hash":"traces","id":5},{"text":"Context Propagation","hash":"context-propagation","id":6},{"text":"Correlation","hash":"correlation","id":7},{"text":"Instrumenting Telemetry","hash":"instrumenting-telemetry","id":8},{"text":"Step 1: Add the MicroProfile Telemetry Dependency","hash":"step-1-add-the-microprofile-telemetry-dependency","id":9},{"text":"Step 2: Create a Tracer","hash":"step-2-create-a-tracer","id":10},{"text":"Step 3: Create a Span","hash":"step-3-create-a-span","id":11},{"text":"Step 4: Add Attributes to the Span","hash":"step-4-add-attributes-to-the-span","id":12},{"text":"Step 5: End the Span","hash":"step-5-end-the-span","id":13},{"text":"Tools for Trace Analysis","hash":"tools-for-trace-analysis","id":14},{"text":"OpenTelemetry Collector","hash":"opentelemetry-collector","id":15},{"text":"Jaeger","hash":"jaeger","id":16},{"text":"Zipkin","hash":"zipkin","id":17},{"text":"Grafana Tempo","hash":"grafana-tempo","id":18},{"text":"Exporting the Traces","hash":"exporting-the-traces","id":19},{"text":"Verify the Traces","hash":"verify-the-traces","id":20},{"text":"Run Jaeger","hash":"run-jaeger","id":21},{"text":"Types of Telemetry","hash":"types-of-telemetry","id":22},{"text":"Automatic Instrumentation","hash":"automatic-instrumentation","id":23},{"text":"Manual Instrumentation","hash":"manual-instrumentation","id":24},{"text":"Using the @WithSpan Annotation","hash":"using-the-withspan-annotation","id":25},{"text":"Using SpanBuilder for Custom Spans","hash":"using-spanbuilder-for-custom-spans","id":26},{"text":"Manual Tracing in PaymentService","hash":"manual-tracing-in-paymentservice","id":27},{"text":"Agent Instrumentation","hash":"agent-instrumentation","id":28},{"text":"Analyzing Traces","hash":"analyzing-traces","id":29},{"text":"Visualizing Traces","hash":"visualizing-traces","id":30},{"text":"Identifying Bottlenecks","hash":"identifying-bottlenecks","id":31},{"text":"Diagnosing Failures","hash":"diagnosing-failures","id":32},{"text":"Understanding Service Dependencies","hash":"understanding-service-dependencies","id":33},{"text":"Correlating Traces with Logs and Metrics","hash":"correlating-traces-with-logs-and-metrics","id":34},{"text":"Best Practices for Analyzing Traces","hash":"best-practices-for-analyzing-traces","id":35},{"text":"Security Considerations for Tracing","hash":"security-considerations-for-tracing","id":36},{"text":"Avoid Capturing Sensitive Data","hash":"avoid-capturing-sensitive-data","id":37},{"text":"Encrypt Trace Data","hash":"encrypt-trace-data","id":38},{"text":"Limit Trace Retention","hash":"limit-trace-retention","id":39},{"text":"Access Control and Auditing","hash":"access-control-and-auditing","id":40},{"text":"Sampling Strategies to Minimize Exposure","hash":"sampling-strategies-to-minimize-exposure","id":41},{"text":"Compliance with Regulations","hash":"compliance-with-regulations","id":42},{"text":"Isolate Tracing Infrastructure","hash":"isolate-tracing-infrastructure","id":43},{"text":"Monitor and Alert on Trace Anomalies","hash":"monitor-and-alert-on-trace-anomalies","id":44},{"text":"Conclusion","hash":"conclusion","id":45}]},"19":{"id":19,"text":"In modern microservices architectures, where services are distributed and stateless, securing communications between clients and services and between individual services is critical. JSON Web Token (JWT) provides a lightweight, self-contained, and efficient user authentication and authorization mechanism, enabling scalable and secure identity propagation across distributed systems. MicroProfile JWT is a specification that standardizes JWT-based authentication and authorization for Java microservices. Leveraging the JWT open standard RFC 7519 enables services to securely extract and validate claims such as identity, roles, and permissions. MicroProfile JWT allows developers to build secure, interoperable, and portable microservices. It supports role-based access control (RBAC), simplifies identity management in stateless services, and avoids vendor lock-in by adhering to open specifications. Introduction to JWT Authentication Structure of JWT Use cases for JSON Web Tokens Benefits of JWT in Microservices Setting up MicroProfile JWT Configuring MicroProfile JWT Validation Request Flow in MicroProfile JWT Role-Based Access Control (RBAC) Setting Token Expiry Times for Security Error Handling Best Practices for JWT Authentication Security Best Practices for Microservices Conclusion This section will explore JSON Web Tokens, how they work, and why they are foundational to implementing stateless authentication and authorization in microservices-based systems. A JSON Web Token (JWT) (see JWT.io), as defined in RFC 7519, is an open standard for securely transmitting information (claims) between parties as a JSON object. JWTs are digitally signed, ensuring their integrity and authenticity. A JWT consists of three Base64 encoded parts, separated by dots (.):
.. Header - It contains metadata about the token, such as token type (type: “JWT”) and signing algorithm ( alg: “RS256” for RSA-SHA256). { \"alg\": \"RS256\", \"typ\": \"JWT\" } Payload - It contains claims which are key-value pairs representing data about the user, such as roles and expiration. Example of claims in a JWT payload: { \"iss\": \"https://io.microprofile.com/issuer\", \"sub\": \"user1\", \"exp\": 1735689600, \"iat\": 1735686000, \"aud\": \"my-audience\", \"groups\": [\"user\", \"admin\"] } Signature — A digital signature that verifies the token’s integrity by combining the encoded header, payload, and private key. Example JWT Token: eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52 YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a 1rZgN5TiysnmzTROF869lQ. AxY8DCtDaGlsbGljb3RoZQ. MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8. fiK51VwhsxJ-siBMR-YFiA Claims in JWTs can be categorized into two types: These are predefined claims with specific meanings, as defined by the JWT specification. Some commonly used standard claims include: Claim Description Example iss Issuer, the entity that issued the JWT (e.g., an authentication server) \"iss\": \"https://io.microprofile.com/issuer\" sub Subject, the principal (user or service) that the JWT is about. \"sub\": \"user1\" aud Audience, the intended recipients of the token (e.g., specific microservices). \"aud\": \"order-service\" exp Expiration time \"exp\": 1735689600 nbf not before \"nbf\": 1735686000 iat issued at, when that token was issued. \"iat\": 1735686000 jti Unique JWT token identifier \"jti\": \"a1b2c3d4\" groups Groups, list of roles or users allowed to access the resource [\"user\", \"admin\"] These are application-specific claims that provide additional information about the user or entity. They can extend authorization logic with application-specific claims (e.g., department, region). Custom claims are not part of the JWT specification but often include domain-specific data, such as user preferences, tenant IDs, or other metadata. MicroProfile JWT allows developers to access these claims programmatically. JWTs are versatile tokens commonly used in modern applications for authentication, where they verify the identity of a user or service; for authorization, where they grant access to resources based on roles or permissions; and for information exchange, where they securely transmit data between parties. Below are key scenarios where JWTs shine in microservices environments: JWTs enable stateless authentication in distributed systems. When a user logs in, an authentication service issues a JWT containing claims like sub (user ID) and exp (expiration time). The client sends this token in the Authorization: Bearer header of subsequent requests, allowing microservices to verify the user’s identity without requiring repeated authentication. For example, a user authenticates with an Auth Service and receives a JWT. This JWT token grants access to other services, such as a product catalog or order management system, without re-authentication. JWTs are also used for authorization, enabling fine-grained access control based on user roles or permissions. The JWT payload typically includes a group or role claim specifying the user’s roles or permissions. For example, a user with the admin role might be allowed to access all resources while a user with the user role might only have access to specific resources. MicroProfile JWT integrates seamlessly with Jakarta EE’s @RolesAllowed annotation, making it easy to enforce role-based access control (RBAC) in microservices. Role mapping can be configured in microprofile-config.properties: mp.jwt.verify.roles=groups JWTs are often used to represent claims-based identity, where the JWT contains claims representing the user’s identity, such as their name, email address, or other attributes. Applications can use these claims to identify the user and personalize their experience. For example, an application might use the email claim to look up the user’s profile information in a database or display the user’s name on a welcome page using the name claim. JWTs can securely exchange information between parties. The token payload can include custom claims representing the data being exchanged, such as an order ID or user ID. This makes JWTs useful in scenarios like Single Sign-On (SSO) systems, where information needs to be shared across multiple services. For example, a JWT might contain an order_id claim and a user_id claim, which an order management service can use to retrieve and display the user’s order details. JWTs facilitate identity federation by allowing integration of multiple trusted identity providers (e.g., Active Directory, LDAP) to provide a single sign-on (SSO) experience. In this case, the JWT contains claims representing the user’s identity, which applications can use to identify the user and retrieve their profile information. For example, an enterprise SSO system can issue a JWT that grants access to HR, Payroll, and CRM microservices. MicroProfile JWT validates the token’s iss (issuer) and aud (audience) to enforce trust boundaries. JWTs are widely used in microservices for the following reasons: JWTs eliminate the need for centralized session storage. Each token is self-contained, embedding all necessary user claims (e.g., roles, permissions) in its payload. Independent Validation: Microservices validate JWTs locally using public keys, avoiding calls to a central authority. This reduces latency and scales horizontally. Example: A payment service validates a JWT’s signature without querying an authentication server. Open Standards: JWTs adhere to RFC 7519, ensuring compatibility across platforms (Java, .NET, Node.js) and frameworks (Spring Boot, Quarkus). MicroProfile Integration: MicroProfile JWT standardizes validation and claim extraction, enabling seamless interoperability across Java microservices. Role-Based Access Control (RBAC): Map JWT claims (e.g., groups) to Jakarta EE roles using @RolesAllowed. Propagation Across Services: A JWT issued by an authentication service is propagated across microservices (e.g., the Order Service and the Inventory Service). Each service independently verifies the token and enforces access control. Reduced Central Dependency: No need for a central authorization server, simplifying architecture and improving fault tolerance. Example: Authentication Service: Issues a JWT with sub: \"user1\" and groups: [\"user\"]. Order Service: Validates the JWT and processes requests if groups include users. Inventory Service: Revalidates the same JWT without contacting the auth service. To use MicroProfile JWT in your project, add the following dependency to your pom.xml (for Maven): org.eclipse.microprofile.jwt microprofile-jwt-auth-api 2.1 provided For Gradle, add the following to your build.gradle: implementation 'org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1' MicroProfile JWT requires validation rules configuration to be defined in src/main/resources/microprofile-config.properties file. Below is an example configuration: # Public key (PEM format) to verify JWT signatures mp.jwt.verify.publickey.location=META-INF/publicKey.pem # Expected issuer (e.g., your OIDC provider) mp.jwt.verify.issuer=https://auth.example.com # Optional: Validate token audience mp.jwt.verify.audiences=order-service,payment-service Explanation: The mp.jwt.verify.publickey.location property specifies the location of the public key used to verify the JWT’s signature. The mp.jwt.verify.issuer property defines the expected issuer of the JWT, ensuring that tokens are only accepted if issued by a trusted authority. Optionally, the mp.jwt.verify.audiences property can specify the allowed audiences for the JWT, ensuring that the token is intended for the service. Place the PEM-encoded public key in src/main/resources/META-INF/publicKey.pem. This key is used to verify incoming JWT signatures. Understanding how JWTs are propagated and processed in a microservices architecture is critical to implementing secure and scalable authentication. This section explains the lifecycle of a JWT from client to service, including token extraction, validation, and claim usage. JWTs are propagated via the Authorization: Bearer HTTP header across clients and services. When a client authenticates (e.g., via a login endpoint), it receives a JWT from an authentication service. This token is then included in the header of subsequent requests to microservices. For example, a request header might look like this: GET /api/orders HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs… In microservices architecture, a client sends a JWT token to the initial service (for example, Order Service) using the Authorization: Bearer header. The initial service can forward the same token when calling another backend service (for example, the Inventory Service). Each microservice independently validates the JWT to enforce decentralized, stateless security. In advanced scenarios involving multiple downstream services, careful consideration must be given to validating the JWT’s aud (audience) claim to ensure the token is intended for the target service. MicroProfile JWT runtime handles token extraction and validation automatically. The token is parsed and validated as follows: Header Parsing: The runtime extracts the token from the Bearer schema. Decoding: The JWT is split into its header, payload, and signature components. The token validation involves the following steps: Signature Verification: The public key validates the token’s integrity. Standard Claims Validation: The runtime then validates standard claims: iss: It should match the mp.jwt.verify.issuer configuration property. exp : This checks if the token has not expired. aud : Optionally it checks for the included service(s) in mp.jwt.verify.audiences. If valid, the JWT’s claims populate the SecurityContext. Otherwise, MicroProfile JWT rejects the request with a 401 Unauthorized status. The SecurityContext interface (from Jakarta EE) provides programmatic access to JWT claims. Once a token is validated, MicroProfile JWT injects the JsonWebToken into the SecurityContext, allowing developers to: Retrieve user identity (e.g., sub claim). Check user roles (e.g., groups claim). Access custom claims (e.g., tenant_id claim). @GET @Path(\"/user-profile\") public String getUserProfile(@Context SecurityContext ctx) { JsonWebToken jwt = (JsonWebToken) ctx.getUserPrincipal(); String userId = jwt.getName(); // Extracts the \"sub\" claim Set roles = jwt.getGroups(); // Extracts the \"groups\" claim String tenant = jwt.getClaim(\"tenant_id\"); // Custom claim return \"User: \" + userId + \", Roles: \" + roles + \", Tenant: \" + tenant; } The SecurityContext simplifies working with JWTs, enabling seamless integration with Jakarta EE’s security annotations like @RolesAllowed. By calling securityContext.getUserPrincipal(), the application can obtain the JsonWebToken instance, which contains all the claims from the JWT. MicroProfile JWT simplifies RBAC by mapping JWT claims (e.g., groups or roles) to Jakarta EE roles. This enables declarative security using the @RolesAllowed annotation. This section explains how to configure and use this mapping effectively. MicroProfile JWT seamlessly integrates with Jakarta EE’s @RolesAllowed annotation to enforce role-based access control in microservices. By default, MicroProfile JWT maps roles from the groups claim in the JWT payload to Jakarta EE roles. The groups claim is a standard JWT claim that represents the roles or groups assigned to the user. For example, a JWT payload might include: { \"iss\": \"https://example.com/issuer\", \"sub\": \"user123\", \"groups\": [\"user\", \"admin\"] } In this case, the user has two roles: user and admin. The roles in the groups claim can be used directly with the @RolesAllowed annotation to secure endpoints. @Path(\"/orders\") public class OrderResource { @GET @Path(\"/{id}\") @RolesAllowed(\"user\") // Only users can access this method public Response getOrder(@PathParam(\"id\") String id, @Context SecurityContext ctx) { String user = ctx.getUserPrincipal().getName(); // Fetch order for the user return Response.ok(\"Order for user: \" + user + \", ID: \" + id).build(); } @DELETE @Path(\"/{id}\") @RolesAllowed(\"admin\") // Only admins can access this method public Response deleteOrder(@PathParam(\"id\") String id, @Context SecurityContext ctx) { String admin = ctx.getUserPrincipal().getName(); // Delete order as admin return Response.ok(\"Order deleted by admin: \" + admin + \", ID: \" + id).build(); } } The GET /orders/chapter10 service is accessible to users, whereas the DELETE /orders/chapter10 is only available to users with the admin role. If your JWT uses a claim other than groups to represent roles (e.g., roles or scopes), you can customize the mapping using the mp.jwt.verify.roles property in microprofile-config.properties: # Optional: Map roles from the \"groups\" claim (default behavior) mp.jwt.verify.roles=groups The groups claim is the default claim used for role mapping in MicroProfile JWT Authentication. Therefore, you typically do not need to set the mp.jwt.verify.roles property unless your JWT uses a different claim name. For example, some identity providers (like OAuth 2.0 servers or OpenID Connect providers) might include roles in claims such as roles, permissions, or scope instead of groups. In such cases, update the mapping in your microprofile-config.properties file: mp.jwt.verify.roles=roles This ensures that MicroProfile JWT Authentication correctly maps the roles for use with Jakarta EE security annotations like @RolesAllowed. Token Validation: MicroProfile JWT validates the JWT’s signature and claims. Role Extraction: Roles are extracted from the configured claim (groups by default). Access Control: The @RolesAllowed annotation checks if the user’s roles match the required roles. If not, a 403 Forbidden response is returned. This approach ensures fine-grained security while maintaining compatibility with standard JWT practices. Short token expiry times reduce the surface area for the attackers. Here’s how to configure token expiry effectively: Set the exp claim at issuance: Ensure your authentication service issues tokens with the exp claim. { \"exp\": 1735689600 // Token expires at 2025-01-01 00:00:00 UTC } MicroProfile JWT automatically validates the exp claim during token verification. Beyond standard JWT validation settings, no additional configuration is needed. MicroProfile JWT will reject tokens returning a 401 Unauthorized response if: The exp claim is missing or invalid. The current time exceeds the exp value. MicroProfile JWT automatically validates tokens and rejects invalid requests with standardized HTTP responses. Common scenarios include: HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error=\"invalid_token\" HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error=\"invalid_token\", error_description=\"Token expired\" HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error=\"missing_token\" HTTP/1.1 403 Forbidden Use Standard Claims - Prefer the groups claim for roles unless your identity provider uses a different claim. Consistent Role Names - Ensure role names (e.g., admin, user) are consistent across JWTs and @RolesAllowed annotations. Least Privilege - Assign minimal required roles to endpoints to reduce security risks. Combine with Other Annotations - Use @PermitAll or @DenyAll alongside @RolesAllowed for flexible security policies. But with more services comes more complexity, and with more complexity comes a greater risk of security breaches. So, how do you secure your microservices? Securing microservices requires a layered approach, combining authentication, authorization, encryption, and monitoring. MicroProfile JWT simplifies access control while adhering to industry standards. Below are best practices tailored for MicroProfile JWT implementations: Enforce Authentication with Validated JWTs: Ensure every request to a microservice includes a valid JWT. Configure MicroProfile JWT to validate tokens using a public key. Reject tokens with invalid signatures, missing claims, or expired exp values. Implement Role-Based Access Control: Restrict endpoint access based on user roles defined in the JWT. Configure role mapping in microprofile-config.properties if using non-default claims Use Short-Lived Tokens: To minimize exposure to compromised tokens, set short expiration times (exp claim) for JWTs (e.g., 15–30 minutes). Secure Token Transmission: Prevent token interception or tampering by using HTTPS to encrypt data in transit and store tokens in HTTP Authorization: Bearer headers (never in URLs or cookies). Manage Cryptographic Keys Securely: Protect keys to sign/verify JWTs by storing public keys in secure locations (e.g., Kubernetes Secrets, AWS KMS). Rotate keys periodically and avoid hardcoding them in source control. Validate and Sanitize JWT Claims: Validate all claims (e.g., iss, aud) in microprofile-config.properties, and Sanitize custom claims before use to prevent injection attacks and misuse of claims. Monitor and Log Security Events: Log JWT validation errors, role mismatches, and token expiration events to detect breaches and audit access patterns. Integrate with monitoring tools (e.g., Prometheus, Grafana) to track anomalies. These steps will help you secure your microservices against the most common attacks. MicroProfile JWT offers a standards-based, interoperable approach for securing microservices. It simplifies identity propagation, access control, and stateless security across distributed services. Integrating with Jakarta EE enables secure, scalable, and interoperable authentication without a session state. Further Reading: RFC 7519 MicroProfile JWT 2.1 Spec Jakarta Security 3.0","title":"JWT Authentication","component":"microprofile-tutorial","version":"6.1","name":"chapter10","url":"/microprofile-tutorial/6.1/chapter10/chapter10.html","titles":[{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":1},{"text":"Introduction to JWT Authentication","hash":"introduction-to-jwt-authentication","id":2},{"text":"What is a JSON Web Token (JWT)?","hash":"what-is-a-json-web-token-jwt","id":3},{"text":"Structure of a JWT","hash":"structure-of-a-jwt","id":4},{"text":"Types of Claims in MicroProfile JWT","hash":"types-of-claims-in-microprofile-jwt","id":5},{"text":"Standard Claims","hash":"standard-claims","id":6},{"text":"Custom Claims","hash":"custom-claims","id":7},{"text":"Use cases for JSON Web Tokens","hash":"use-cases-for-json-web-tokens","id":8},{"text":"Authentication","hash":"authentication","id":9},{"text":"Authorization (Role-Based Access Control)","hash":"authorization-role-based-access-control","id":10},{"text":"Claims-based identity","hash":"claims-based-identity","id":11},{"text":"Information Exchange","hash":"information-exchange","id":12},{"text":"Federation & Single Sign-On (SSO)","hash":"federation-single-sign-on-sso","id":13},{"text":"Benefits of using JWT in Microservices","hash":"benefits-of-using-jwt-in-microservices","id":14},{"text":"Statelessness & Scalability","hash":"statelessness-scalability","id":15},{"text":"Interoperability","hash":"interoperability","id":16},{"text":"Fine-Grained Authorization","hash":"fine-grained-authorization","id":17},{"text":"Decentralized Security","hash":"decentralized-security","id":18},{"text":"Setting Up MicroProfile JWT","hash":"setting-up-microprofile-jwt","id":19},{"text":"Configuring MicroProfile JWT Validation","hash":"configuring-microprofile-jwt-validation","id":20},{"text":"Public Key Setup","hash":"public-key-setup","id":21},{"text":"Request Flow in MicroProfile JWT","hash":"request-flow-in-microprofile-jwt","id":22},{"text":"How JWTs are Propagated in Microservices","hash":"how-jwts-are-propagated-in-microservices","id":23},{"text":"Client-to-Service","hash":"client-to-service","id":24},{"text":"Service-to-Service","hash":"service-to-service","id":25},{"text":"Token Extraction","hash":"token-extraction","id":26},{"text":"Token Validation","hash":"token-validation","id":27},{"text":"Accessing JWT claims via SecurityContext","hash":"accessing-jwt-claims-via-securitycontext","id":28},{"text":"Role-Based Access Control (RBAC)","hash":"role-based-access-control-rbac","id":29},{"text":"Default Role Mapping with the groups Claim","hash":"default-role-mapping-with-the-groups-claim","id":30},{"text":"Securing Endpoints","hash":"securing-endpoints","id":31},{"text":"Custom Role Mapping","hash":"custom-role-mapping","id":32},{"text":"How the RBAC Works","hash":"how-the-rbac-works","id":33},{"text":"Setting Token Expiry Times for Security","hash":"setting-token-expiry-times-for-security","id":34},{"text":"Configuring Token Expiry","hash":"configuring-token-expiry","id":35},{"text":"Error Handling","hash":"error-handling","id":36},{"text":"Invalid Token (e.g., malformed JWT, invalid signature):","hash":"invalid-token-e-g-malformed-jwt-invalid-signature","id":37},{"text":"Expired Token (exp claim validation failure):","hash":"expired-token-exp-claim-validation-failure","id":38},{"text":"Missing Token","hash":"missing-token","id":39},{"text":"Insufficient Permissions (e.g., missing role for @RolesAllowed):","hash":"insufficient-permissions-e-g-missing-role-for-rolesallowed","id":40},{"text":"Best Practices for JWT Authentication","hash":"best-practices-for-jwt-authentication","id":41},{"text":"Security Best Practices for Microservices","hash":"security-best-practices-for-microservices","id":42},{"text":"Conclusion","hash":"conclusion","id":43}]},"20":{"id":20,"text":"In microservices architecture, developers often face the cumbersome task of implementing boilerplate code to consume REST APIs - manually constructing HTTP requests, parsing responses, and handling errors. The MicroProfile Rest Client specification addresses this by leveraging Jakarta RESTful Web Services (formerly JAX-RS) annotations to create type-safe Rest client interfaces. Instead of writing low-level HTTP logic, developers define Java interfaces that mirror the target service’s endpoints. At runtime, MicroProfile Rest Client dynamically generates an implementation of these interfaces, automating HTTP communication while ensuring compile-time consistency between the client and server contracts. This chapter introduces the MicroProfile Rest Client, a type-safe framework for simplifying service-to-service communication. We will begin by defining REST client interfaces using Jakarta RESTful Web Services annotations (@GET, @Path), configuring endpoints via MicroProfile Config, and implementing HTTP invocation. Next, we will explore handling HTTP communication, processing responses, and error handling. By the end of this chapter, you will be able to replace hand-written HTTP boilerplate code with declarative, maintainable clients while adhering to Jakarta EE and MicroProfile standards. Introduction to MicroProfile Rest Client Setting up Dependencies Defining a Rest Client Interface Parameter Configuration Requests and Response Handling Working with JSON Data formats Error Handling Strategies The MicroProfile Rest Client specification simplifies RESTful service consumption in Java microservices by replacing error-prone manual HTTP handling with a type-safe, annotation-driven approach. Instead of writing boilerplate code, developers define Java interfaces that mirror the target service’s API. Using Jakarta RESTful Web Services annotations like @GET, and @Path, these interfaces declaratively map methods to HTTP operations (e.g., /users/{id} to getUser(id)). The framework then generates an implementation at runtime, automating communication while ensuring compile-time consistency between client and server contracts. Tight integration with MicroProfile Config and CDI allows seamless configuration and injection, making it ideal for building resilient, maintainable clients that align with modern microservices practices. The MicroProfile Rest Client simplifies consuming RESTful services in Java microservices with the following key features: Type-Safe and Declarative APIs - The MicroProfile Rest Client allows developers to define REST clients as Java interfaces using Jakarta RESTful Web Services annotations like @GET, @POST, @PUT, @DELETE, @Path, @Consumes and @Produces. This approach improves code clarity and ensures compile-time validation, reducing the possibility of runtime errors . Integration with CDI (Context and Dependency Injection) - This specification allows developers to seamlessly inject MicroProfile Rest Client interfaces using @Inject and @RestClient into CDI-managed beans, promoting better dependency management and integration with other components. By leveraging CDI lifecycle management, the MicroProfile Rest Client can benefit from scope management (e.g., @ApplicationScoped), proxying, and automatic initialization. Runtime Configurable with MicroProfile Config - The behavior of MicroProfile Rest Client can be dynamically configured using MicroProfile Config. This allows properties like the base URL and other client settings to be adjusted without recompilation. The configuration can be provided through microprofile-config.properties or environment variables, making the client highly adaptable to different environments. Support for Asynchronous Execution - For asynchronous execution, MicroProfile Rest Client can return CompletionStage, allowing non-blocking requests. This significantly improves performance & scalability in high-concurrency environments. Automatic Handling of Redirect Responses - MicroProfile Rest Client can automatically follow HTTP redirects, simplifying client implementation when working with services that return 3xx responses. Secure Socket Layer (SSL) and Security Configuration - Supports SSL/TLS configuration, including certificates and trust stores, ensuring secure communication between microservices. Propagation of Headers and Cookies - Enables automatic propagation of HTTP headers, cookies and context (e.g., authentication tokens), facilitating session management across service calls. Exception Handling and Custom Providers - Allows custom exception mapping and response handling, giving developers control over error response based on specific conditions, improving fault tolerance and user experience. Integration with MicroProfile Fault Tolerance - This specification Supports resilience patterns like retries (@Retry), circuit breakers (@CircuitBreaker), and Bulkheads (@Bulkhead), ensuring stability in service-to-service communications. Integration with MicroProfile Long Running Actions (LRA) - MicroProfile Rest Client can coordinate distributed transactions using LRA annotations (e.g., @LRA), enabling compensation logic for long-running processes. This ensures consistency across services in complex workflows. Portability and Standards Compliance: This specification enables MicroProfile Rest Client to work across different MicroProfile-compatible runtimes, leveraging Jakarta EE standards (CDI, Jakarta RESTful Web Services, JSON Binding, JSON Processing). To use MicroProfile Rest Client 3.1 in your project, you need to include the necessary dependencies in your build configuration. Below are configurations for Maven and Gradle: For Maven-based projects, add the following dependency to your pom.xml file: org.eclipse.microprofile.rest.client microprofile-rest-client-api 3.1 For Gradle-based projects, add the following dependency to your build.gradle file: dependencies { Implementation 'org.eclipse.microprofile.rest.client:microprofile-rest-client-api:3.1' compileOnly 'org.eclipse.microprofile:microprofile:6.1' } Tip: The MicroProfile Rest Client is an Eclipse Foundation project. For more details and updates on the project, visit the official repository: MicroProfile Rest Client on GitHub. To create a MicroProfile Rest Client interface, you need to define a Java interface and annotate it with annotations to map it to a RESTful service. To use the MicroProfile Rest Client, annotate your client interface with @RegisterRestClient. This annotation registers the interface as a Rest client within MicroProfile runtime and enables it as a CDI bean, allowing it to be injected into other components. Example: package io.microprofile.tutorial.inventory.client; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.microprofile.tutorial.inventory.dto.Product; @RegisterRestClient(configKey = \"product-service\") @Path(\"/products\") public interface ProductServiceClient { @GET @Path(\"/{id}\") Product getProductById(@PathParam(\"id\") Long id); } Explanation: In the above code, we define a ProductServiceClient within the package io.microprofile.tutorial.inventory.client. The interface serves as a Rest client for interaction with a remote product service. @RegisterRestClient - declares the ProductServiceClient interface as a MicroProfile Rest Client, enabling it to be injected into other CDI-managed components. configKey = \"product-service\" - associates the client with a configuration key, allowing dynamic configuration via MicroProfile Config (e.g., using microprofile-config.properties or environment variables). @Path(/products) - specifies the base URI path segement for the RESTful service. @GET - indicates that the getProductById() method handles HTTP GET requests. @Path(\"/{id}\") – define a dynamic URI path parameter {id}, which will be replaced at runtime with the actual value provided. @PathParam(\"id\") - binds the method parameter id to the {id} placeholder in the request URL. Return Type (Product) - specifies that the method returns a Product Data Transfer Object (DTO), representing the retrieved product data. Note: In CDI environments, it is recommended not to extend AutoCloseable in REST client interfaces. The container manages the lifecycle of injected clients automatically, ensuring proper resource handling without requiring manual closure. To configure the URI using MicroProfile Config, you need to add a config file named src/main/webapp/META-INF/microprofile-config.properties in your project. This file contains the configuration key and value pairs. In this example, we’re configuring the base URI to http://localhost:8080/api/products. We can configure other client properties, such as followRedirects. The followRedirects property specifies whether the client should automatically follow HTTP redirects (3xx status codes) when making RESTful web service calls. product-service/mp-rest/url=http://localhost:8080/api/products product-service/mp-rest/followRedirects=true In MicroProfile Rest Client, you can dynamically configure headers, query parameters, and path parameters using Jakarta RESTful Web Services annotations. These annotations bind method parameters to different parts of the HTTP request, enabling flexible and dynamic RESTful client interfaces that can efficiently interact with various endpoints. Supported Parameter Annotations @PathParam – Binds a method parameter to a path variable in the URL. @QueryParam – Maps a method parameter to a query string parameter in the request URL. @HeaderParam – Attaches a method parameter to an HTTP request header. Path parameters are used to insert dynamic values directly into the URL path. import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @RegisterRestClient @Path(\"/products\") public interface ProductServiceClient { @GET @Path(\"/{id}\") Product getProductById(@PathParam(\"id\") Long id); } Example productServiceClient.getProductById(1L); Resulting HTTP Request GET /products/1 Ensures URL structure consistency by enforcing path variables Prevents hardcoding URLs, making the code cleaner and maintainable. Query parameters are typically used for filtering, pagination, or optional parameters in the request URL. Example: import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @RegisterRestClient @Path(\"/products\") public interface ProductServiceClient { @GET List getProductsByCategory(@QueryParam(\"category\") String category); } Example Call: productServiceClient.getProductsByCategory(\"electronics\"); Resulting HTTP Request: GET /products?category=electronics Useful for filtering results (?category=electronics). Ideal for pagination (?page=2&size=20). Allows sending optional parameters without modifying the URL structure. Header parameters are typically used for authentication, authorization, and metadata transmission between client and server. Example: import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.Path; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @RegisterRestClient @Path(\"/orders\") public interface OrderServiceClient { @GET List getOrders(@HeaderParam(\"Authorization\") String authToken); } Example Call: orderServiceClient.getOrders(\"Bearer my-secret-token\"); Resulting HTTP Request: GET /orders Authorization: Bearer my-secret-token Used for passing authentication tokens (Authorization: Bearer token). Helps with custom metadata exchange (e.g., X-Correlation-ID: 12345). Avoids exposing sensitive data in URLs (e.g., API keys). @CookieParam - Binds a method parameter to the value of an HTTP cookie in the incoming request. @FormParam — Maps a method parameter to a field in a submitted HTML form (application/x-www-form-urlencoded POST body). @MatrixParam — Binds a method parameter to a matrix parameter embedded within the URL path segements (e.g., /product;color=blue;size=large). @BeanParam — Aggregates multiple parameter annotations (path, query, header, etc.) into a single Java bean for cleaner method signature. Tip: These annotations eliminate manual string concatenation, making REST client calls type-safe and maintainable. In MicroProfile Rest Client, handling requests and responses involves defining methods in your interface that map to RESTful service endpoints. This ensures that: HTTP requests are automatically constructed based on method definitions. Responses are efficiently deserialized into Java objects (DTOs) or processed manually using Response. Using Jakarta RESTful Web Services annotations, you can define standard HTTP operations such as @GET, @POST, @PUT, and @DELETE. The framework also supports additional methods like @HEAD, @OPTIONS, and @PATCH, providing complete control over HTTP communication when needed. Meanwhile, MicroProfile automatically handles serialization, deserialization, and request execution at runtime. By default, MicroProfile Rest Client supports JSON format without requiring additional configurations. Serialization and deserialization of request and response bodies are automatically handled using JSON-B (Jakarta JSON Binding) or JSON-P (Jakarta JSON Processing). Developers can directly use Java objects as request bodies or response entities, eliminating the need for manual parsing. Example: import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.Consumes; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @RegisterRestClient @Path(\"/products\") @Produces(\"application/json\") @Consumes(\"application/json\") public interface ProductServiceClient { @GET @Path(\"/{id}\") Product getProductById(@PathParam(\"id\") Long id); } Explanation The @Produces(\"application/json\") annotation specifies that the client expects JSON responses. This determines the value of the Accept header in HTTP requests. The @Consumes(\"application/json\") annotation specifies that the client sends JSON requests. This determines the value of the Content-Type header of the request. By default the media type \"application/json\" is used if @Produces and @Consumes are not explicitly set. MicroProfile Rest Client automatically serializes Java objects to JSON and deserializes responses into Product DTO (Data Transfer Object) Java object. Effective error handling is crucial when consuming remote RESTful services. MicroProfile Rest Client provides a structured approach to error handling by mapping HTTP responses to exceptions using the ResponseExceptionMapper interface. This mechanism allows developers to: Convert specific HTTP response codes into custom exceptions. Customize exception handling behavior at runtime. Automatically throw mapped exceptions in client invocations. The ResponseExceptionMapper interface allows mapping an HTTP Response object to a Throwable (custom exception). This improves error handling by ensuring meaningful exceptions are thrown instead of manually checking response codes. How it Works Scanning and Prioritizing Exception Mappers: When a client method is invoked, the runtime scans all registered ResponseExceptionMapper implementations. Mappers are then sorted in ascending order of priority, determined by the @Priority annotation. The mapper with the lowest numeric priority value is checked first. Handling Responses: The handles(int status, MultivaluedMap headers) method determines whether a mapper should handle a given response. By default, it handles responses with status code 400 or higher, but we can override this behavior. Converting the Response to an Exception: The toThrowable(Response response) method converts a response into a Throwable (exception). Checked exceptions are only thrown if the client method declares that it throws that type of exception of its superclass. Unchecked exceptions (RuntimeException) are always thrown. Example: package io.microprofile.tutorial.inventory.client; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; @RegisterRestClient(configKey = \"product-service\") @RegisterProvider(ProductServiceResponseExceptionMapper.class) @Path(\"/products\") public interface ProductServiceClient extends AutoCloseable { @GET @Path(\"/{id}\") Response getProductById(@PathParam(\"id\") Long id); } Explanation: The REST client interface defines an endpoint for retrieving products. The @RegisterProvider annotation registers ProductServiceResponseExceptionMapper, ensuring custom exception handling. And below is the corresponding ResponseExceptionMapper: package io.microprofile.tutorial.inventory.client; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import io.microprofile.tutorial.inventory.dto.ProductNotFoundException; public class ProductServiceResponseExceptionMapper implements ResponseExceptionMapper { @Override public Throwable toThrowable(Response response) { if (response.getStatus() == 404) { return new ProductNotFoundException(\"Product not found\"); } return new Exception(\"An unexpected error occurred\"); } } Explanation: If the response status code is 404, a ProductNotFoundException is thrown. Otherwise, a generic exception is returned. While CDI-based injection is commonly used for REST clients in MicroProfile, programmatic creation using the RestClientBuilder class is beneficial when CDI is unavailable or when dynamic client instantiation is required. This builder provides a fluent API for configuring and constructing REST client proxies without relying on constructors that require numerous arguments. Using RestClientBuilder simplifies object creation, improves code readability, and supports method chaining, where each configuration method returns the builder instance itself. In the MicroProfile Ecommerce Store, the InventoryService must verify whether a product exists before checking or updating inventory. This interaction can be handled by calling the ProductService using a REST client interface. package io.microprofile.tutorial.store.inventory.service; import io.microprofile.tutorial.store.inventory.client.ProductServiceClient; import io.microprofile.tutorial.store.product.entity.Product; import org.eclipse.microprofile.rest.client.RestClientBuilder; import java.net.URI; import java.util.concurrent.TimeUnit; public class InventoryService { public boolean isProductAvailable(Long productId) { URI productApiUri = URI.create(\"http://localhost:8080/api\"); try (ProductServiceClient productClient = RestClientBuilder.newBuilder() .baseUri(productApiUri) .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.SECONDS) .build(ProductServiceClient.class)) { Product product = productClient.getProductById(productId); return product != null; } catch (Exception e) { // Log exception (omitted for brevity) return false; } } } The isProductAvailable() method accepts a product ID and returns true if the product exists in the catalog. A URI object is created pointing to the base path of the ProductService API using URI.create(). A ProductServiceClient instance is created using the builder pattern inside a try-with-resource block: newBuilder() initializes the client builder. baseUri() sets the root endpoint of the target service. connectTimeout() and readTimeout() define connection and read timeouts respectively. build() finalizes and returns the configured client proxy. Because ProductServiceClient extends AutoCloseable, the try-with-resources block ensures that the client is automatically closed after the operation, preventing resource leaks. If a Product object is successfully returned, true is returned. Any exceptions are caught and handled appropriately, returning false in case of failure. This approach is especially useful for utility services, batch jobs, or environments where REST client configuration must be dynamic or conditional, and manual client lifecycle management is necessary. Tip: When building MicroProfile REST clients programmatically (using RestClientBuilder), ensure that your client interface extends AutoCloseable and uses try-with-resources to release resources automatically. The MicroProfile Rest Client provides a declarative, type-safe, and efficient mechanism for interacting with RESTful services in Java microservices. It reduces boilerplate code and lets developers focus on core business logic while still offering fine-grained control through features like RestClientBuilder. By integrating seamlessly with other MicroProfile specifications—such as Config, Fault Tolerance, and JWT Authentication—the Rest Client helps enhance the security, resilience, and maintainability of cloud-native applications. Removes boilerplate HTTP code, improving clarity and maintainability. Automatically handles JSON serialization and deserialization. Supports CDI injection for managed client lifecycles. Integrates with Fault Tolerance for retries, timeouts, and circuit breakers. Enhances security through header propagation and authentication mechanisms. With MicroProfile Rest Client, building robust and maintainable microservices that communicate over REST becomes simpler, more flexible, and more powerful. This concludes the MicroProfile tutorial. You are now equipped with the foundational knowledge to build robust, cloud-native microservices using the MicroProfile specification. Thank you for following along, and happy coding!","title":"MicroProfile Rest Client","component":"microprofile-tutorial","version":"6.1","name":"chapter11","url":"/microprofile-tutorial/6.1/chapter11/chapter11.html","titles":[{"text":"Topics to be covered:","hash":"topics-to-be-covered","id":1},{"text":"Introduction to MicroProfile Rest Client","hash":"introduction-to-microprofile-rest-client","id":2},{"text":"Key Features of MicroProfile Rest Client","hash":"key-features-of-microprofile-rest-client","id":3},{"text":"Setting up Dependency for MicroProfile Rest Client","hash":"setting-up-dependency-for-microprofile-rest-client","id":4},{"text":"Maven Configuration","hash":"maven-configuration","id":5},{"text":"Gradle Configuration","hash":"gradle-configuration","id":6},{"text":"Creating MicroProfile Rest Client Interface","hash":"creating-microprofile-rest-client-interface","id":7},{"text":"The @RegisterRestClient Annotation","hash":"the-registerrestclient-annotation","id":8},{"text":"Configuration via MicroProfile Config:","hash":"configuration-via-microprofile-config","id":9},{"text":"Parameter Configurations","hash":"parameter-configurations","id":10},{"text":"Using Path Parameters (@PathParam)","hash":"using-path-parameters-pathparam","id":11},{"text":"Why Use @PathParam?","hash":"why-use-pathparam","id":12},{"text":"Using Query Parameters (@QueryParam)","hash":"using-query-parameters-queryparam","id":13},{"text":"Why Use @QueryParam?","hash":"why-use-queryparam","id":14},{"text":"Using Header Parameters (@HeaderParam)","hash":"using-header-parameters-headerparam","id":15},{"text":"Why Use @HeaderParam?","hash":"why-use-headerparam","id":16},{"text":"Overview of Additional Annotations","hash":"overview-of-additional-annotations","id":17},{"text":"Handling Requests and Responses","hash":"handling-requests-and-responses","id":18},{"text":"Handling JSON Data formats","hash":"handling-json-data-formats","id":19},{"text":"Error Handling","hash":"error-handling","id":20},{"text":"Using ResponseExceptionMapper interface","hash":"using-responseexceptionmapper-interface","id":21},{"text":"Using the RestClientBuilder Class","hash":"using-the-restclientbuilder-class","id":22},{"text":"Example: Inventory Service Calls Product Service","hash":"example-inventory-service-calls-product-service","id":23},{"text":"Explanation","hash":"explanation","id":24},{"text":"Conclusion","hash":"conclusion","id":25},{"text":"Key Takeaways","hash":"key-takeaways","id":26}]}},"components":{},"componentVersions":{"microprofile-tutorial/6.1":{"displayVersion":"6.1","title":"MicroProfile Tutorial","version":"6.1","name":"microprofile-tutorial","asciidoc":{"attributes":{"env":"site","env-site":"","site-gen":"antora","site-gen-antora":"","attribute-missing":"warn","data-uri":null,"icons":"font","sectanchors":"","source-highlighter":"highlight.js","site-title":"MicroProfile Tutorial","site-url":"https://microprofile.io","experimental":true,"idprefix":"","idseparator":"-","page-pagination":true,"allow-uri-read":"","page-pdf-download-name":"microprofile-tutorial.pdf","source-language":"asciidoc@","table-caption":false,"xrefstyle":"full"},"sourcemap":false,"extensions":[{},{},{}],"keepSource":true},"url":"/microprofile-tutorial/6.1/index.html","navigation":[{"items":[{"content":"Preface","url":"/microprofile-tutorial/6.1/index.html","urlType":"internal"},{"content":"Introduction to MicroProfile","url":"/microprofile-tutorial/6.1/chapter01/chapter01.html","urlType":"internal"},{"content":"Getting Started with MicroProfile","url":"/microprofile-tutorial/6.1/chapter02/chapter02-00.html","urlType":"internal"},{"content":"Jakarta EE 10 Core Profile","url":"/microprofile-tutorial/6.1/chapter03/chapter03.html","urlType":"internal"},{"content":"MicroProfile OpenAPI","url":"/microprofile-tutorial/6.1/chapter04/chapter04.html","urlType":"internal"},{"content":"MicroProfile Configuration","url":"/microprofile-tutorial/6.1/chapter05/chapter05.html","urlType":"internal"},{"content":"MicroProfile Health","url":"/microprofile-tutorial/6.1/chapter06/chapter06.html","urlType":"internal"},{"content":"MicroProfile Metrics","url":"/microprofile-tutorial/6.1/chapter07/chapter07.html","urlType":"internal"},{"content":"MicroProfile Fault Tolerance","url":"/microprofile-tutorial/6.1/chapter08/chapter08.html","urlType":"internal"},{"content":"MicroProfile Telemetry","url":"/microprofile-tutorial/6.1/chapter09/index.html","urlType":"internal"},{"content":"MicroProfile JWT","url":"/microprofile-tutorial/6.1/chapter10/chapter10.html","urlType":"internal"},{"content":"MicroProfile Rest Client","url":"/microprofile-tutorial/6.1/chapter11/chapter11.html","urlType":"internal"}],"root":true,"order":0}]}}}}) \ No newline at end of file diff --git a/build/site/sitemap.xml b/build/site/sitemap.xml new file mode 100644 index 00000000..88d45183 --- /dev/null +++ b/build/site/sitemap.xml @@ -0,0 +1,83 @@ + + + +https://microprofile.io/microprofile-tutorial/6.1/chapter01/chapter01.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-00.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-01.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-02.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-03.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-04.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-05.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02-06.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter02/chapter02.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter03/chapter03.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter04/chapter04.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter05/chapter05.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter06/chapter06.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter07/chapter07.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter08/chapter08.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter09/index.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter10/chapter10.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/chapter11/chapter11.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/index.html +2025-05-27T15:41:15.490Z + + +https://microprofile.io/microprofile-tutorial/6.1/README.html +2025-05-27T15:41:15.490Z + + diff --git a/chapter01/chapter01.adoc b/chapter01/chapter01.adoc index 7a72d89e..d35d8fcb 100644 --- a/chapter01/chapter01.adoc +++ b/chapter01/chapter01.adoc @@ -32,6 +32,7 @@ Java user groups Corporate Members + * Hunan AsiaInfo Anhui * IBM * Fujitsu * Red Hat diff --git a/chapter02/chapter02-00.adoc b/chapter02/chapter02-00.adoc index 45b1cd19..993ada16 100644 --- a/chapter02/chapter02-00.adoc +++ b/chapter02/chapter02-00.adoc @@ -33,7 +33,7 @@ To install JDK, follow the steps below: [source, bash] ---- -java -version +java -version. ---- You should an output similar to the one below: @@ -48,7 +48,7 @@ This confirms that JDK 17 has been successfully installed on your system. NOTE: For most MicroProfile implementations, JDK 11 or later is recommended. In this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK -providers such as Oracle JDK, Amazon Corretto, Azul Zulu, OpenJ9 also offer +providers such as Oracle JDK, Amazon Corretto, and Azul Zulu also offer compatible JDK distributions. === Build Tools (Maven or Gradle) @@ -59,7 +59,7 @@ Build tools like link:https://maven.apache.org/[Apache Maven] or link:https://gr * *Gradle*: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It's a great choice for complex projects requiring customization. -IMPORTANT: If your existing project's build uses Maven wrapper (`mvnw`) or Gradle wrapper (`gradlew`), you don't have to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. +IMPORTANT: If your existing project's build uses Maven wrapper (`mvnw`) or Gradle wrapper (`gradlew`), you don't to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. ==== Installing Apache Maven @@ -164,7 +164,7 @@ MicroProfile applications need a runtime that supports MicroProfile specificatio ==== Open Liberty -link:https://openliberty.io/[Open Liberty] is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease. Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance. +link:https://openliberty.io/[Open Liberty] is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease.Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance. link:https://openliberty.io/start/[Downloading Open Liberty] page provides access to its latest releases and documentation to help you set up your environment. diff --git a/chapter02/chapter02-01.adoc b/chapter02/chapter02-01.adoc index deafbd4b..1dd2dc93 100644 --- a/chapter02/chapter02-01.adoc +++ b/chapter02/chapter02-01.adoc @@ -154,4 +154,4 @@ Below is the list of essential dependencies you need to add to your Maven _pom.x These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​. -TIP: Execute the `$ mvn validate` command. This checks the _pom.xml_ file for correctness, ensuring that all necessary configuration is present and valid. +TIP: Execute the `$ mvn validate command`. This checks the _pom.xml_ file for correctness, ensuring that all necessary configuration is present and valid. diff --git a/chapter02/chapter02-02.adoc b/chapter02/chapter02-02.adoc index b1d4fb7d..38081804 100644 --- a/chapter02/chapter02-02.adoc +++ b/chapter02/chapter02-02.adoc @@ -147,7 +147,7 @@ The list below is provided to help you select the appropriate modules for your M io.opentelemetry opentelemetry-bom - 1.29.0 + 1.35.0 pom import diff --git a/chapter02/chapter02-03.adoc b/chapter02/chapter02-03.adoc index e4a1cc88..05b5edc1 100644 --- a/chapter02/chapter02-03.adoc +++ b/chapter02/chapter02-03.adoc @@ -96,7 +96,7 @@ public class ProductResource { Explanation: -* The `ProductResource` is annotated with `@ApplicationScoped`. This will ensure that only one instance of this class available when the +* The `ProductResource` is annotated with `@ApplicationScoped`. This will ensure that this class is available as long as the application is running. * The `ProductResource` class has a `getProducts()` method, which returns a list of products. This method is annotated with the `@GET` annotation, which maps this method to the `GET` HTTP method. diff --git a/chapter02/chapter02-04.adoc b/chapter02/chatper02-04.adoc similarity index 100% rename from chapter02/chapter02-04.adoc rename to chapter02/chatper02-04.adoc diff --git a/chapter08 b/chapter08 deleted file mode 100644 index 8b137891..00000000 --- a/chapter08 +++ /dev/null @@ -1 +0,0 @@ - diff --git a/dev-server.sh b/dev-server.sh new file mode 100755 index 00000000..7b095e70 --- /dev/null +++ b/dev-server.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Script to build and serve the MicroProfile Tutorial documentation +# with automatic rebuilding when files change + +# Check if inotifywait is installed (part of inotify-tools) +if ! command -v inotifywait &> /dev/null; then + echo "inotify-tools is required but not installed. Installing..." + apt-get update && apt-get install -y inotify-tools || { + echo "Failed to install inotify-tools. Please install it manually." + exit 1 + } +fi + +# Check if a simple HTTP server is available +if ! command -v python3 &> /dev/null; then + echo "Python 3 is required but not installed. Please install it." + exit 1 +fi + +# Function to build the site +build_site() { + echo "Building documentation site..." + antora antora-assembler.yml && ./fix-edit-links.sh + echo "Build completed!" +} + +# Function to serve the site +serve_site() { + echo "Starting HTTP server on port 8080..." + echo "Open your browser at http://localhost:8080" + echo "Press Ctrl+C to stop" + (cd build/site && python3 -m http.server 8080) +} + +# Function to watch for file changes +watch_files() { + echo "Watching for file changes..." + + # Build once initially + build_site + + # Watch for changes in AsciiDoc files, YAML configs, and UI files + while true; do + inotifywait -r -e modify,create,delete \ + --include '.*\.adoc$|.*\.yml$|.*\.hbs$|.*\.css$|.*\.js$' \ + ./modules ./supplemental-ui ./*.yml + + # Rebuild when changes are detected + echo "Changes detected, rebuilding..." + build_site + done +} + +# Main script logic +case "$1" in + build) + build_site + ;; + serve) + build_site + serve_site + ;; + watch) + # Start the HTTP server in the background + (serve_site) & + server_pid=$! + + # Trap Ctrl+C to kill the server process + trap "kill $server_pid; exit" INT TERM + + # Watch for file changes and rebuild + watch_files + ;; + *) + echo "Usage: $0 {build|serve|watch}" + echo "" + echo "Commands:" + echo " build Build the documentation site" + echo " serve Build and serve the site locally" + echo " watch Build, serve, and automatically rebuild on changes" + exit 1 + ;; +esac + +exit 0 diff --git a/fix-edit-links.sh b/fix-edit-links.sh new file mode 100755 index 00000000..296e9f16 --- /dev/null +++ b/fix-edit-links.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Script to fix Edit This Page links in Antora site +# This script replaces local file:// links with GitHub repository links + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Base directory for the site +SITE_DIR="/workspaces/microprofile-tutorial/build/site" + +# GitHub repository information +REPO_URL="https://github.com/ttelang/microprofile-tutorial" +BRANCH="patch-15" +PATH_PREFIX="modules/ROOT/pages/" + +# Check if the site directory exists +if [ ! -d "$SITE_DIR" ]; then + echo -e "${RED}Error: Site directory not found at $SITE_DIR${NC}" + echo -e "${YELLOW}Did you run 'antora antora-assembler.yml' first?${NC}" + exit 1 +fi + +echo -e "${BLUE}Fixing Edit This Page links in HTML files...${NC}" + +# Count of files processed and fixed +total_files=0 +fixed_files=0 + +# Find all HTML files and process them +find "$SITE_DIR" -name "*.html" -type f | while read -r file; do + ((total_files++)) + + # Check if the file contains any file:// links that need fixing + if grep -q "file:///workspaces/microprofile-tutorial/\./modules/ROOT/pages/" "$file"; then + # Replace file:// links with GitHub links + sed -i "s|file:///workspaces/microprofile-tutorial/\./modules/ROOT/pages/|${REPO_URL}/edit/${BRANCH}/${PATH_PREFIX}|g" "$file" + ((fixed_files++)) + echo -e "${GREEN}✓${NC} Fixed links in: ${YELLOW}$(basename "$file")${NC}" + fi +done + +# Summary +echo -e "\n${BLUE}Summary:${NC}" +echo -e " - Total HTML files processed: ${total_files}" +echo -e " - Files with links fixed: ${fixed_files}" +echo -e "\n${GREEN}Edit links successfully fixed!${NC}" + +# Provide hint about repository URL +echo -e "\n${YELLOW}Note:${NC} If you change the repository URL or branch, update the following in this script:" +echo -e " - REPO_URL (currently: ${REPO_URL})" +echo -e " - BRANCH (currently: ${BRANCH})" diff --git a/images/src/figureFM-1.drawio b/images/FM-1 E-Comm MS Arch Diagram.drawio similarity index 100% rename from images/src/figureFM-1.drawio rename to images/FM-1 E-Comm MS Arch Diagram.drawio diff --git a/images/README.md b/images/README.md new file mode 100644 index 00000000..f4f79d20 --- /dev/null +++ b/images/README.md @@ -0,0 +1,5 @@ +The following table list images and their corresponding source files. + +| Export | Source | Tool | +----------|---------|-------- +| FigrueFM-1.png | FM-1 MicroProfie e-Commerce.drawio | https://www.draw.io | diff --git a/images/figure1-2.png b/images/figure1-2.png index a9d1a072..979617be 100644 Binary files a/images/figure1-2.png and b/images/figure1-2.png differ diff --git a/index.adoc b/index.adoc index 3b3a62af..de4f03f3 100644 --- a/index.adoc +++ b/index.adoc @@ -1,4 +1,5 @@ = MicroProfile API Tutorial +:id: index :doctype: book ---- diff --git a/minimal-component/antora.yml b/minimal-component/antora.yml new file mode 100644 index 00000000..400b8443 --- /dev/null +++ b/minimal-component/antora.yml @@ -0,0 +1,5 @@ +name: minimal-component +title: Minimal Component +version: latest +start_page: minimal-component::index.adoc +start_path: docs diff --git a/modules/ROOT/assets/images/favicon.png b/modules/ROOT/assets/images/favicon.png new file mode 100644 index 00000000..53ada59e --- /dev/null +++ b/modules/ROOT/assets/images/favicon.png @@ -0,0 +1,7 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ + diff --git a/images/src/figure1-2 MP_Arch_Philosophy.drawio b/modules/ROOT/assets/src/figure1-2 MP_Arch_Philosophy.drawio similarity index 100% rename from images/src/figure1-2 MP_Arch_Philosophy.drawio rename to modules/ROOT/assets/src/figure1-2 MP_Arch_Philosophy.drawio diff --git a/modules/ROOT/assets/src/figureFM-1.drawio b/modules/ROOT/assets/src/figureFM-1.drawio new file mode 100644 index 00000000..f377902f --- /dev/null +++ b/modules/ROOT/assets/src/figureFM-1.drawio @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/ROOT/images/figure1-2.png b/modules/ROOT/images/figure1-2.png new file mode 100644 index 00000000..a9d1a072 Binary files /dev/null and b/modules/ROOT/images/figure1-2.png differ diff --git a/modules/ROOT/images/figure4-1.png b/modules/ROOT/images/figure4-1.png new file mode 100644 index 00000000..86d4b0e3 Binary files /dev/null and b/modules/ROOT/images/figure4-1.png differ diff --git a/modules/ROOT/images/figure8-1.png b/modules/ROOT/images/figure8-1.png new file mode 100644 index 00000000..122a31d2 Binary files /dev/null and b/modules/ROOT/images/figure8-1.png differ diff --git a/modules/ROOT/images/figureFM-1.png b/modules/ROOT/images/figureFM-1.png new file mode 100644 index 00000000..934b5a46 Binary files /dev/null and b/modules/ROOT/images/figureFM-1.png differ diff --git a/modules/ROOT/pages/README.adoc b/modules/ROOT/pages/README.adoc new file mode 100644 index 00000000..5974a09e --- /dev/null +++ b/modules/ROOT/pages/README.adoc @@ -0,0 +1,20 @@ += README +:doctype: book + +== Overview + +image:https://badges.gitter.im/eclipse/microprofile-samples.svg[link="https://app.gitter.im/#/room/#eclipse/microprofile-tutorial"] + +This repo contains the source files that are used to build the _MicroProfile Tutorial_. The source files are authored in link:https://asciidoc.org/[AsciiDoc]. AsciiDoc is similar to markdown but is particularly suited for user documentation. The source files are processed and integrated into the MicroProfile Tutorial site using link:https://antora.org/[Antora], which is a tool for building documentation sites. + +== Running the Site Locally + +After building the site, you can serve it locally using Python's built-in HTTP server: + +[source,bash] +---- +cd build/site +python3 -m http.server 8080 +---- + +Then open your browser and navigate to http://localhost:8080 to view the site. diff --git a/modules/ROOT/pages/chapter01/chapter01.adoc b/modules/ROOT/pages/chapter01/chapter01.adoc new file mode 100644 index 00000000..89de68af --- /dev/null +++ b/modules/ROOT/pages/chapter01/chapter01.adoc @@ -0,0 +1,280 @@ += Introduction to MicroProfile + +== Introduction + +This introductory chapter provides a comprehensive overview of the MicroProfile platform, setting the stage for subsequent chapters in this tutorial. It aims to familiarize you with the fundamentals of the MicroProfile platform, its need, and benefits. Finally, we will discuss its place in the broader context of enterprise Java development. + +== Topics to be covered: +- What is MicroProfile +- Need for MicroProfile +- MicroProfile Specifications +- Current MicroProfile Implementations +- Architecture Philosophy +- Benefits of MicroProfile +- Relationship with Jakarta EE specification + +== What is MicroProfile + +link:https://microprofile.io/[MicroProfile] is an open-source specification that enhances enterprise Java technologies for microservices development. It provides a set of APIs and specifications for building modern, scalable, resilient, and efficient microservices-based applications. The primary goal of MicroProfile is to simplify the development for Java developers, enabling them to create applications optimized for cloud-native-development. + +MicroProfile was initiated in June 2016 by a collaboration of industry leaders, Java community members, and individual contributors. In the following year, the project was transitioned to the link:https://www.eclipse.org/[Eclipse Foundation] to enhance the project's openness and vendor-neutral stance. Now, MicroProfile has become a key framework for extending Java in the cloud-computing domain, offering a comprehensive suite of APIs tailored for developing microservices in a cloud-native ecosystem. + +The *MicroProfile Working Group* currently comprises of the following members: + +Committer Representative (Year 2024) + + * Emerson Castañeda + +Java user groups + + * Atlanta Java User Group (AJUG) + * Association of the German Java User Groups (iJUG) + +Corporate Members + + * IBM + * Fujitsu + * Red Hat + * Primeton + * Payara + * Microsoft + * Tomitribe + * Oracle + +This collective effort demonstrates MicroProfile's commitment to evolving Java enterprise development for the modern cloud environment, leveraging the expertise of its community. + +== Need for MicroProfile + +The MicroProfile Specification was developed to address the following requirements: + +- *Microservices Architecture Adoption*: The industry shift towards microservices architecture has brought several advantages, including improved flexibility, scalability, and speed of deployment. However, it also introduced several new challenges for developers due to the added complexities. These include ensuring seamless integration between microservices, securing each microservice individually as well as interactions between them, managing performance and efficiency, designing microservices to be fault-tolerant and resilient to failures, ensuring data consistency across services, managing configurations across multiple environments and managing various independently deployable components. To address these challenges, MicroProfile provides a simplified and optimized set of APIs designed to build and deploy Java-based microservices applications. + +- *Limitations of Traditional Enterprise Java*: Traditional enterprise Java frameworks, like Java EE (now Jakarta EE), were often seen as too monolithic and heavyweight for microservices while evolving too slowly. It led to a demand for a more streamlined and microservices-focused framework. MicroProfile fills this gap by providing a lightweight alternative optimized for microservices development. + +- *Cloud-Native Application Development*: The rise of cloud-native applications necessitated new features such as external configuration, health checks, and fault tolerance, which existing Java standards did not adequately address. MicroProfile bridges these gaps left, making it easier for developers to create resilient, scalable, and manageable microservices for cloud-native application development using Java. + +- *Community-Driven Innovation*: The rapid pace of technological change in microservices necessitated a collaborative platform for innovation. MicroProfile, backed by community and vendor support, promotes rapid evolution to meet these demands. + +- *Vendor Neutrality and Interoperability*: There was a need for a framework that could provide standardization across different implementations and environments, ensuring compatibility and avoiding vendor lock-in. + +- *Focus on Simplicity and Productivity*: Developers needed a simple, easy-to-understand framework that increased productivity by reducing boilerplate code and focusing on essential microservice functionalities. Well-defined standards and patterns eliminate the need to reinvent the wheel, allowing developers to focus on microservices logic. + +- *Support for familiar programming model*: MicroProfile was founded with support for Jakarta JSON Processing, Jakarta JSON Binding, Jakarta RESTful Web Services, and Jakarta Contexts and Dependency Injection (CDI) to define the core programming model and accelerate adoption. + +- *Lightweight and Resilient Services*: With the microservices architecture, there's a need for frameworks that support the development of lightweight, resilient, and independently deployable services, which are essential for microservices. + +- *Rapid Adaptation to New Trends*: The technology landscape, especially around microservices, is constantly evolving. A framework like MicroProfile, which is community-driven and rapidly evolving, can adapt quickly to these changes, continually incorporating new practices and technologies, including: + ** *Streaming APIs and Reactive Programming Model*: To facilitate non-blocking communication and data processing, enhancing system responsiveness and scalability. + ** *API-First Development (Open API)*: Emphasizing the design and documentation of microservices with an API-first approach, promoting interoperability and clear service contracts. + ** *Eventual Consistency and Long Running Actions (LRA)*: Addressing the challenges of data consistency in distributed systems without compromising system performance. + +- *Enhanced Observability and Monitoring*: Microservices architectures complicate application monitoring and observability. A framework with built-in support for these capabilities simplifies the management of distributed services. + +== MicroProfile Specifications + +MicroProfile specifications are divided into two main categories: Platform and Standalone. + +:figure-caption: Figure +.MicroProfile Specifications +image::http://microprofile.io/wp-content/uploads/2023/10/microprofile_release_6.1.png[MicroProfile 6.1] + +=== MicroProfile Platform Component Specifications + +The MicroProfile Platform Specification is the core set of MicroProfile specifications designed to provide the foundational functionalities needed for microservices development. These specifications solve specific microservices challenges, including configuration, fault tolerance, health checks, metrics, and security. The table below provides a list of platform specifications of MicroProfile along with their descriptions: + +[options="header"] +|======================= +|Specification |Description +|link:https://microprofile.io/specifications/microprofile-config/[Config] | Provides an easy-to-use and flexible system for application configuration. +|link:https://microprofile.io/specifications/microprofile-fault-tolerance/[Fault Tolerance]| Implements patterns like Circuit Breaker, Bulkhead, Retry, Timeout, and Fallback for building resilient applications. +|link:https://microprofile.io/specifications/microprofile-jwt-auth/[JWT Authentication]| Defines a standard for using OpenID Connect (OIDC) based JSON Web Tokens(JWT) for role-based access control(RBAC) of microservices endpoints for secure communication. +|link:https://microprofile.io/specifications/microprofile-metrics/[Metrics] | Define custom application metrics and expose platform metrics on a standard endpoint using a standard format to external monitoring systems. +|link:https://microprofile.io/specifications/microprofile-health/[Health] | Allows applications to expose their health and readiness to perform operations to the underlying platform, which is crucial for automated recovery in cloud environments. +|link:https://microprofile.io/specifications/microprofile-open-api/[Open API] | Facilitates the generation of OpenAPI documentation for RESTful services, making API discovery and understanding easier. +|link:https://microprofile.io/specifications/microprofile-telemetry/[Telemetry]| Provides a unified set of APIs, libraries, and tools for collecting, processing, and exporting telemetry data (metrics, traces, and logs) from cloud-native applications and services. +|link:https://microprofile.io/specifications/microprofile-rest-client[Rest Client]| Defines a type-safe approach to invoke RESTful services over HTTP(S), simplifying the development of Rest clients. +| link:https://jakarta.ee/specifications/coreprofile/10/[Jakarta EE Core Profile 10] | An optimized Jakarta EE platform designed specifically for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. +|======================= + +=== Standalone (Outside Umbrella) Specifications + +Standalone specifications address more advanced needs that every microservices application may not require. They allow for innovation and experimentation in areas that are evolving or where there’s a need to address niche concerns without burdening the core platform with additional complexity. The table below provides a list of standalone specifications of MicroProfile along with their descriptions: + +[options="header"] +|======================= +|Specification |Description +| link:https://microprofile.io/specifications/microprofile-context-propagation/[Context Propagation] | Defines a way to propagate context between threads and managed executor services. Ensure that the context is consistent during executing asynchronous tasks or across different services. +| link:https://microprofile.io/specifications/microprofile-graphql/[GraphQL] | Provides a layer on top of Jakarta EE that allows the creation of GraphQL services. This specification makes it easier to build APIs, enabling clients to request exactly the data they need and nothing more. +| link:https://microprofile.io/specifications/microprofile-lra/[Long Running Actions (LRA)]| Focuses on providing a model for developing services that participate in long-running processes, ensuring consistency and reliability without necessarily locking data. +| link:https://microprofile.io/specifications/microprofile-reactive-messaging/[Reactive Messaging]| Aims to facilitate building applications that communicate via reactive streams, enabling the development of event-driven, responsive, and resilient microservices. +| link:https://microprofile.io/specifications/microprofile-reactive-streams-operators/[Reactive Streams Operators]| Provides a way to process data streams in a reactive manner, allowing for non-blocking system design and improving the efficiency of data processing in microservices. +| link:https://microprofile.io/specifications/microprofile-opentracing/[Open Tracing]| Integrates distributed tracing by defining a way for services to trace requests across service boundaries, improving observability. +|======================= + +== MicroProfile Implementations +Below is the list of MicroProfile Implementations, each offering a platform for building and running microservices-based applications: + +- link:https://www.payara.fish/products/payara-micro/[Payara Micro^] +- link:https://tomee.apache.org/[Apache TomEE^] +- link:https://openliberty.io/[Open Liberty^] +- link:https://github.com/fujitsu/launcher[Launcher^] +- link:https://quarkus.io/[Quarkus^] +- link:https://www.wildfly.org/[WildFly^] +- link:https://helidon.io/[Helidon^] + +== Architecture Philosophy + +:imagesdir: ../assets/images + +The overall goal of MicroProfile architecture is to provide a lightweight enterprise-grade framework tailored for building cloud-native applications and enabling developers to build and deploy microservices with Java easily: + +- *Simplicity*: MicroProfile APIs are designed to be simple and easy to use. They avoid unnecessary complexity and focus on providing the essential functionality for building microservices. + +- *Modularity*: Its modular approach allows developers to use only what they need, reducing the overhead typically associated with enterprise frameworks. + +- *Standards-based*: MicroProfile is based on open standards and specifications, ensuring compatibility and consistency across different implementations. + +- *Community-driven*: It encourages active participation from the Java community for continuous evolution. + +- *Vendor-Neutral*: MicroProfile is vendor-neutral. It’s supported by several industry players, ensuring that no single company controls its direction. + +- *Focus on Cloud-Native Applications*: The architecture is specifically tailored for cloud environments. MicroProfile integrates with a number of cloud-native technologies, such as Kubernetes and Istio. This makes it easy to deploy and manage MicroProfile applications in cloud environments. + +- *Reactive programming*: MicroProfile supports reactive programming, which is a style of programming that is well-suited for building microservices. Reactive applications are responsive and scalable, and they can handle high volumes of concurrent requests. + +:figure-caption: Figure +.Architecture Philosophy of MicroProfile +image::figure1-2.png[MicroProfile Architecture Philosophy] + +=== Benefits of MicroProfile +MicroProfile offers several benefits, making it a compelling choice for developing microservices, especially in Java-centric environments. These benefits include: + +- *Optimized for Microservices*: MicroProfile is designed explicitly for creating microservices, offering APIs that cater to the unique challenges of this architectural style. + +- *Cloud-Native Focus*: The framework includes features such as externalized configuration, health checks, and metrics, which are essential for building and operating cloud-native applications effectively. MicroProfile is inherently designed for cloud-native applications. + +- *Open Source and Standards-Based*: As an open-source framework based on open standards, MicroProfile facilitates interoperability and reduces the risk of vendor lock-in. + +- *Enhanced Productivity, Rapid Development and Deployment*: MicroProfile simplifies microservices development with a set of standard APIs. With its focus on simplicity and productivity, MicroProfile helps speed up the development and deployment of microservices by providing essential functionalities and reducing boilerplate code. + +- *Community-Driven Innovation*: Being community-driven, MicroProfile evolves quickly, incorporating new trends and best practices in microservices development. MicroProfile is backed by a strong Java community, ensuring continuous improvement and support. + +- *Vendor Neutrality*: Being vendor-neutral, MicroProfile is supported by a wide range of industry players, which ensures a broad choice of tools and platforms for developers. + +- *Compatibility with Jakarta EE*: MicroProfile is complementary to Jakarta EE, whether using MicroProfile implementations that support a small subset of Jakarta EE (such as Core Profile) or implementations that extend the full Jakarta EE Platform implementations with MicroProfile. + +- *Lightweight and Modular*: It provides a lightweight model compared to traditional enterprise Java frameworks. Its modularity allows developers to use only the necessary components, reducing the application's footprint and overhead. + +- *Scalability*: The framework supports the development of scalable applications, essential for microservices that handle varying loads efficiently. + +- *Enhanced Resilience*: MicroProfile includes specifications for fault tolerance patterns like retries, circuit breakers, timeouts, and bulkheads, which are crucial for building resilient services that can withstand network and service failures. + +- *Security Features*: MicroProfile's JWT Authentication provides a standardized way to secure microservices, making it easier to implement authentication and authorization. + +- *Ease of Testing*: With its lightweight nature and support for advanced features like Rest Client, MicroProfile simplifies the testing of microservices, both in isolation and in integration scenarios. + +== Relationship with Jakarta EE specification + +Jakarta EE is an open specification with more than 40 component specifications to address a wide array of needs of enterprise Java development. MicroProfile complements this by providing a baseline platform definition that optimizes enterprise Java for microservices architecture and delivers application portability across multiple compatible runtimes. Many Jakarta EE implementations that target a broad array of applications supplement Jakarta EE with MicroProfile to better support microservices. Their coexistence allows developers to harness the strength of both platforms, thereby facilitating a more versatile and adaptive approach to modern enterprise and cloud-native application development. MicroProfile strategically leverages Java EE developers' existing skill sets, enabling them to transition and adapt to microservices development with minimal learning curve. This ensures that developers can easily design and implement microservices architecture, enhancing productivity and facilitating the creation of cloud-native applications. Later in this tutorial, we will explore how MicroProfile API extends Jakarta EE’s capability to address microservices-specific challenges. + +NOTE: MicroProfile and Jakarta EE are complementary technologies. Both platforms enable developers to stay at the forefront of cloud-native application development. + +== Conclusion + +In this section, we explored the MicroProfile platform in detail, laying the foundation for understanding how it revolutionizes the development of microservices using Java. We started by defining MicroProfile, emphasizing its role as an open-source specification tailored for microservices development. Key contributions from industry leaders and community members have positioned MicroProfile as a pivotal technology in the Java ecosystem, especially for cloud-native application development. We delved into the essential specifications of MicroProfile, each playing a critical role in addressing specific challenges in microservices development, from configuration management to service resilience. As we move forward in this tutorial, we will delve deeper into each specification and discover how to implement MicroProfile in real-world Java applications effectively. + +[[glossary]] +== Glossary + +[[microservices]] +Microservices:: An architectural style for building applications as a collection of small, independent services. Each service focuses on a specific business capability and communicates with other services through well-defined APIs. + +[[apis]] +APIs (Application Programming Interfaces):: A set of definitions and protocols that specify how software components interact with each other. + +[[cloud-native-development]] +Cloud-native development:: An approach to building and running applications that are specifically designed for the cloud environment. It involves using technologies and practices that leverage the benefits of cloud platforms, such as scalability, elasticity, and pay-as-you-go pricing. + +[[eclipse-foundation-working-group]] +Eclipse Foundation Working Group:: A collaborative group of industry leaders and Java community members who actively contribute to the of development of Eclipse projects like MicroProfile within the Eclipse Foundation framework. + +[[jakarta-ee]] +Jakarta EE:: Jakarta EE (formerly Java Platform, Enterprise Edition, or Java EE) is a set of specifications, extending Java Platform, Standard Edition, or Java SE with specifications for enterprise features such as web services, database persistence, asynchronous messaging and more. + +[[external-configuration]] +External Configuration:: A technique in application development where configuration data is separated from the application code, allowing the application's behavior to be adjusted without changing the code, especially useful in cloud-native and microservices architectures. + +[[health-checks]] +Health Checks:: Mechanisms used in microservices architectures to continuously check the status of an application or service to ensure it is functioning correctly and available to users. + +[[fault-tolerance]] +Fault Tolerance:: The ability of a system to continue operating in the event of the failure of some of its components. This feature is critical for maintaining high availability and reliability in microservices architectures. + +[[vendor-neutrality]] +Vendor Neutrality:: The principle of designing software products and standards not controlled by any single vendor, promoting user interoperability and choice. + +[[interoperability]] +Interoperability:: The ability of a software to exchange and make use of information across different platforms and services. + +[[json-p]] +JSON-P (JSON Processing):: A Jakarta EE (formerly Java EE) API that enables parsing, generating, transforming, and querying JSON data. It facilitates the processing of JSON data within the Java programming environment. Currently it is known as Jakarta JSON Processing. + +[[json-b]] +JSON-B (JSON Binding):: A Jakarta EE (formerly Java EE) API for binding Java objects to JSON messages and vice versa, streamlining the serialization and deserialization process. It allows custom mappings to handle complex conversion scenarios efficiently. Currently it is known as Jakarta JSON Binding. + +[[jax-rs]] +JAX-RS (Java API for RESTful Web Services):: A Jakarta EE API for creating web services according to the REST architectural pattern in Java, using annotations to simplify development. It enables the easy creation and management of resources via standard HTTP methods. It is currently known as Jakarta RESTful Web Services. + +[[cdi]] +CDI (Contexts and Dependency Injection):: A Jakarta EE API for enterprise-grade dependency injection, offering type-safe mechanisms, context lifecycle management, and a framework for decoupling application components. It enhances modularity and facilitates the development of loosely coupled, easily testable applications. + +[[boilerplate-code]] +Boilerplate Code:: A piece of code that must be included in many places with little or no alteration. + +[[lightweight-services]] +Lightweight Services:: Services designed to consume minimal computing resources, enhancing performance and efficiency, particularly relevant in a microservices architecture. + +[[resilient-services]] +Resilient Services:: Services built to recover quickly from failures and continue operating. It is critical for maintaining the reliability of microservices-based applications. + +[[observability]] +Observability:: The ability to measure the internal state of a system by examining its outputs, crucial for understanding the performance and behavior of microservices. + +[[monitoring]] +Monitoring:: The practice of tracking and logging the performance and status of applications and infrastructure, essential for maintaining system health in microservices environments. + +[[circuit-breaker]] +Circuit Breaker:: A fault tolerance mechanism that prevents a failure in one service from causing system-wide failure, by temporarily disabling failing services. + +[[bulkhead]] +Bulkhead:: A pattern that isolates failures in one part of a system from the others, ensuring that parts of an application can continue functioning despite issues elsewhere. + +[[retry]] +Retry:: A simple fault tolerance mechanism where an operation is attempted again if it fails initially, based on predefined criteria. + +[[timeout]] +Timeout:: A mechanism to limit the time waiting for a response from a service, helping to avoid resource deadlock situations in distributed systems. + +[[fallback]] +Fallback:: A fault tolerance mechanism that provides an alternative solution or response when a primary method fails. + +[[rbac]] +Role-Based Access Control (RBAC):: A method of restricting system access to authorized users based on their roles within an organization. + +[[kubernetes]] +Kubernetes:: An open-source platform for automating deployment, scaling, and operations of application containers across clusters of hosts. + +[[istio]] +Istio:: An open platform to connect, manage, and secure microservices, providing an easy way to create a network of deployed services with load balancing, service-to-service authentication, and monitoring. + +[[reactive-programming]] +Reactive Programming:: A programming paradigm oriented around data flows and the propagation of change, enabling the development of responsive and resilient systems. + +[[distributed-tracing]] +Distributed Tracing:: A method for monitoring applications, especially those built using a microservices architecture, by tracking the flow of requests and responses across services. + +[[lra]] +Long Running Actions (LRA):: A model for managing long-duration, distributed transactions across microservices without locking resources. + +[[reactive-streams]] +Reactive Streams:: An initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. diff --git a/modules/ROOT/pages/chapter02/chapter02-00.adoc b/modules/ROOT/pages/chapter02/chapter02-00.adoc new file mode 100644 index 00000000..29f4fb22 --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-00.adoc @@ -0,0 +1,224 @@ += Getting Started with MicroProfile + +== Introduction + +In this chapter, you'll embark on your MicroProfile journey! We will guide you through creating your first microservice, equipping you with the essential understanding to leverage this robust framework for building modern, cloud-native applications. This journey begins with setting up your development environment, then diving into creating a microservice. + +== Topics Covered + +- Development Environment Setup +- Configuring Build Tools +- Initializing a New MicroProfile Project +- Choosing Right Modules for your Application +- Building a RESTful service +- Deployment +- Testing your microservices +- Exploring Further with MicroProfile + +== Development Environment Setup + +Let's begin by preparing your workspace for MicroProfile development: + +=== Java Development Kit (JDK) + +MicroProfile, a Java framework, runs on the Java Virtual Machine (JVM), making the Java Development Kit (JDK) an essential component of your development environment. + +To install JDK, follow the steps below: + +. *Download*: Visit the official link:https://openjdk.org/[OpenJDK] website and download the JDK version compatible with your operating system. + +. *Install*: Follow the installation instructions provided on this official link:https://openjdk.org/install/[OpenJDK Installation] guide. + +. *Verify*: After Installation, run the following command in your command line or terminal to verify the Installation: + +[source, bash] +---- +java -version +---- + +You should an output similar to the one below: +---- +openjdk 17.0.10 2024-01-16 LTS +OpenJDK Runtime Environment Microsoft-8902769 (build 17.0.10+7-LTS) +OpenJDK 64-Bit Server VM Microsoft-8902769 (build 17.0.10+7-LTS, mixed mode, +sharing) +---- + +This confirms that JDK 17 has been successfully installed on your system. + +NOTE: For most MicroProfile implementations, JDK 11 or later is recommended. In +this tutorial, we will be using JDK 17. While OpenJDK is used here, other JDK +providers such as Oracle JDK, Amazon Corretto, Azul Zulu, OpenJ9 also offer +compatible JDK distributions. + +=== Build Tools (Maven or Gradle) + +Build tools like link:https://maven.apache.org/[Apache Maven] or link:https://gradle.org/[Gradle] are commonly used for managing project dependencies and building Java applications. You can install the one that best fits your project needs. Here's a brief overview to help you decide: + +* *Apache Maven*: Known for its convention-over-configuration approach, Maven is a popular choice due to its simple project setup and extensive plugin repository. + +* *Gradle*: Offers a flexible, script-based build configuration, allowing for highly customized build processes. Gradle is renowned for its superior performance, due to its incremental builds and caching mechanisms. It's a great choice for complex projects requiring customization. + +IMPORTANT: If your existing project's build uses Maven wrapper (`mvnw`) or Gradle wrapper (`gradlew`), you don't have to install any of these build tools. These wrappers help ensure a consistent build environment without requiring the build tools to be installed on your system. + +==== Installing Apache Maven + +To install Maven follow the steps below: + +. Visit the link:https://https://maven.apache.org/install.html[Installing Apache Maven] web page to download the latest version. + +. Follow the installation instructions provided on the site. + +. Verify the Maven installation by running this command in your terminal or command line. + +---- +mvn -v +---- + +You should see output similar to: + +---- +Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) +Maven home: /usr/local/sdkman/candidates/maven/current +Java version: 17.0.10, vendor: Microsoft, runtime: /usr/lib/jvm/msopenjdk-current +Default locale: en_US, platform encoding: UTF-8 +OS name: "linux", version: "6.2.0-1019-azure", arch: "amd64", family: "unix" +---- + +After Maven is installed, you can configure the _pom.xml_ file in your project to include the MicroProfile dependencies. + +==== Gradle + +To install Gradle follow the step below: + +. Visit the link:https://gradle.org/install/[Gradle | Installation] web page to download the latest version. +. Follow the installation instructions provided on the site. +. Verify the installation by running this command in your terminal or command line. + +---- +gradle -version +---- + +You should see output similar to: + +---- +Welcome to Gradle 8.6! + +Here are the highlights of this release: + - Configurable encryption key for configuration cache + - Build init improvements + - Build authoring improvements + +For more details see https://docs.gradle.org/8.6/release-notes.html + +------------------------------------------------------------ +Gradle 8.6 +------------------------------------------------------------ + +Build time: 2024-02-02 16:47:16 UTC +Revision: d55c486870a0dc6f6278f53d21381396d0741c6e + +Kotlin: 1.9.20 +Groovy: 3.0.17 +Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 +JVM: 17.0.10 (Microsoft 17.0.10+7-LTS) +OS: Linux 6.2.0-1019-azure amd64 +---- + +After Gradle is installed, you can configure the _build.gradle_ file in your project to include the MicroProfile dependencies. + +Whether you opt for Maven's stability and convention or Gradle's flexibility and performance, understanding how to configure and use your chosen build tool is important for MicroProfile development. + +=== Integrated Development Environments + +Integrated Development Environments (IDEs) enhance developer productivity by providing a rich set of features and extensions such as project boostraping, dependency management, intelligent code completion, configuration assistance, test runners, build, hot deployment and debugging tools. For MicroProfile development, the choice of IDE can significantly affect your development speed and efficiency. Below is a list of popular IDEs and their key features related to Java and MicroProfile development: + +==== Eclipse for Enterprise Java and Web Developers + +_Overview_: link:https://www.eclipse.org/downloads/packages/release/2023-12/r/eclipse-ide-enterprise-java-and-web-developers[Eclipse for Enterprise Java and Web Developers] is a widely used IDE for Java development, offering extensive support for Java EE, Jakarta EE, and MicroProfile, among other technologies. + +_Getting Started_: The official Eclipse documentation containing instructions about creating Java projects - link:https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/gettingStarted/qs-3.htm[Creating your first Java Project] + +==== IntelliJ IDEA +_Overview_: link:https://www.jetbrains.com/idea/[IntelliJ IDEA] by JetBrains supports a wide range of programming languages and frameworks, including Java, Kotlin, and frameworks like Spring, Jakarta EE, and MicroProfile. + +_Getting Started_: Refer to this IntelliJ IDEA guide on link:https://www.jetbrains.com/help/idea/2024.1/creating-and-running-your-first-java-application.html[Creating a Java Project Using IntelliJ IDEA 2024.1]. + +==== Apache NetBeans + +_Overview_: link:https://netbeans.apache.org/front/main/[NetBeans] is an open-source IDE that supports Java development, including Java SE, Java EE, JavaFX, and more. + +_Getting Started_: Check out this link:https://netbeans.apache.org/tutorial/main/kb/docs/java/quickstart/[NetBeans Java Quick Start Tutorial] for a tutorial on creating a Java application. + +==== Visual Studio Code + +_Overview_: link:https://code.visualstudio.com/[Visual Studio Code] is a lightweight, powerful source code editor that supports Java development through extensions. + +_Getting Started_: To start with Java in VS Code, follow this link:https://code.visualstudio.com/docs/java/java-tutorial[Getting Started with Java in VS Code] documentation. + +Selecting an IDE should be based on personal preference, as the best choice varies depending on individual needs, familiarity, and the specific features that enhance your productivity. Each IDE offers unique advantages for MicroProfile development. + +=== Setting up MicroProfile Runtime + +MicroProfile applications need a runtime that supports MicroProfile specifications or a MicroProfile-compatible server to run your applications. Below are some popular options, each with unique features tailored to different needs: + +==== Open Liberty + +link:https://openliberty.io/[Open Liberty] is a flexible server framework from IBM that supports MicroProfile, allowing developers to build microservices and cloud-native applications with ease. Open Liberty is known for its dynamic updates and lightweight design, which enhances developer productivity and application performance. + +link:https://openliberty.io/start/[Downloading Open Liberty] page provides access to its latest releases and documentation to help you set up your environment. + +==== Quarkus + +link:https://quarkus.io/[Quarkus] is known for its container-first approach, offering fast startup times and low memory footprint. It aims to optimize Java for Kubernetes and cloud environments + +This link:https://quarkus.io/guides/getting-started[Getting Started with Quarkus] page will guide you through creating your first Quartus project and exploring its cloud-native capabilities. + +==== Payara Micro + +link:https://www.payara.fish/products/payara-micro/[Payara Micro] is a lightweight middleware platform suited for containerized Jakarta EE and MicroProfile applications. + +The link:https://www.payara.fish/downloads/payara-platform-community-edition/[Payara Platform Community Edition] enables easy packaging of applications into a single, runnable JAR file, simplifying deployment and scaling in cloud environments. This site about Payara Platform Community Edition offers downloads and documentation to get started. + +==== WildFly + +link:https://www.wildfly.org/[WildFly] is a flexible, lightweight, managed application runtime that offers full Jakarta EE and MicroProfile support. WildFly is designed for scalability and flexibility in both traditional and cloud-native environments. + +link:https://www.wildfly.org/downloads/[WildFly Downloads] page offers the latest versions and documentation to get you started. + +==== Helidon + +Developed by Oracle, link:https://helidon.io/[Helidon] MP implements MicroProfile specifications. It provides a familiar programming model for Jakarta EE developers and enables efficient microservice development. + +link:https://helidon.io/docs/[Helidon Documentation] provides comprehensive resources to help developers get started with the framework, understand its core concepts, and develop microservices efficiently. + +==== Apache TomEE + +link:https://tomee.apache.org/[Apache TomEE] integrates several Apache projects with Apache Tomcat to provide a Jakarta EE environment. It offers support for MicroProfile, allowing developers to build and deploy microservices using the well-known Jakarta EE technologies with additional MicroProfile capabilities. + +link:https://tomee.apache.org/download.html[TomEE Downloads] and link:https://tomee.apache.org/microprofile-6.0/javadoc/[TomEE MicroProfile Documentation] page provide the necessary resources to get started with TomEE for MicroProfile development. + +=== MicroProfile Starter +To kickstart your MicroProfile project, use the MicroProfile Starter to generate a sample project with your chosen server and specifications. This tool provides a customizable project structure and generates necessary boilerplate code and configuration. + +* Visit the link:https://start.microprofile.io/[MicroProfile] Starter page - the official website for generating the MicroProfile project templates. + +* Provide a `groupId` for your project, it's an identifier for your project and should be unique to avoid conflicts with other libraries or projects. + +TIP: Its recommended convention is to start your `groupId` with the reverse domain name of your organization (for example, `io.microprofile`). + +* Enter the 'artifactID', which is the name of your project (e.g., 'mp-ecomm-store'). + +* Select the *Java SE version* your project will use. + +* Select the *MicroProfile version* you want to use. Ideally, you should choose the latest version for the most up-to-date features but also consider the runtime’s support. + +* Select the specifications you want to include in your project. These could be Config, Fault Tolerance, JWT Auth, Metrics, Health, Open API, Open Tracing, Rest Client. Choose what is relevant to your application. + +* Click the _Download_ button. + +* Unzip the generated project and import it into your IDE. + +This completes the development environment setup. Now we are all set to begin development using MicroProfile. + +IMPORTANT: At the time of writing this tutorial, the latest MicroProfile released version was 6.1. The MicroProfile Starter does not currently support this version. Hence, we will not be using MicroProfile Starter to generate the project structure. diff --git a/modules/ROOT/pages/chapter02/chapter02-01.adoc b/modules/ROOT/pages/chapter02/chapter02-01.adoc new file mode 100644 index 00000000..6914926b --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-01.adoc @@ -0,0 +1,157 @@ +== Creating a Java Project for MicroProfile Development + +=== Using Your IDE + +Most modern IDEs have built-in support for creating Java and Maven projects. Depending on your chosen IDE, look for options like "New Project", or "New Maven Project" to get started. These options typically guide you through the setup process, including specifying the project's `groupId`, `artifactId`, and dependencies. + +=== Using Maven from Command Line (Terminal) + +For developers who prefer using the command line or for those setting up projects in environments without an IDE, Maven can generate the base structure of a Java project through its archetype mechanism. + +To create a project using Maven, open your terminal or command line and run the following command: + +[source, bash] +---- +mvn archetype:generate -DgroupId=io.microprofile.tutorial -DartifactId=store +-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false +---- + +The above command creates a new Maven project based on the `maven-archetype-quickstart archetype`, setting the `groupId` as `io.microprofile.tutorial` and the `artifactID` as `store`. + +Explanation: + +* `mvn archetype:generate` goal in this command initializes the project creation process and instructs Maven to generate a new project based on an archetype template. + +* `-DgroupId=io.microprofile.tutorial` Specifies the groupId for the project. The groupId is a unique identifier for your project and is usually based on the domain name of your organization in reverse. In this case, io.microprofile.tutorial represent a project related to MicroProfile tutorial. + +* `-DartifactId=store`: Sets the `artifactId` for the project. The `artifactId` is the name of the project and is used as the base name for the project's artifacts (e.g., WAR files). Here, `store` is used as the project name to indicate this project is related an e-commerce store application. + +* `-DarchetypeArtifactId=maven-archetype-quickstart`: Indicates which archetype to use for generating the project. The maven-archetype-quickstart is a basic template for a Java application, providing a simple project structure that includes a sample application (App.java) and a unit test (AppTest.java). + +* `-DinteractiveMode=false`: This option disables interactive mode, meaning Maven will not prompt you for input during the project generation process. All necessary information is provided via the command-line options, allowing the command to execute without further user interaction. + +Next, using your file explorer or command line, create the following folder structure: + +[source, plain text] +---- +. +├── pom.xml +├── README.adoc +└── src + └── main + │ └── java + │ └── io + │ └── microprofile + │ └── tutorial + │ └── store + │ └── product + │ │ ├── entity + │ │ │ └── Product.java + │ │ └── resource + │ │ └── ProductResource.java + │ └── ProductRestApplication.java + └── test + └── java + └── io + └── microprofile + └── tutorial + └── store + └── product + └── ProductServiceTest.java +---- + +The standard location for your Java source code is this folder: +---- +src/main/java +---- + +And, the standard location for your test code is this folder: +---- +src/test/java +---- + + +You can delete `App.java` and `AppTest.java` files from the poject as these are not required in MicroProfile development. + +The heart of your Maven project is `pom.xml` (Project Object Model) file. It defines project metadata, dependencies, build configurations and plugins. + +The content for the _pom.xml_ file should look as below, ensure MicroProfile depencency is added: + +[source,xml] +---- + + + + 4.0.0 + + io.microprofile.tutorial + mp-ecomm + 1.0-SNAPSHOT + war + + + 17 + 17 +... +... + + + org.projectlombok + lombok + 1.18.26 + provided + + + + + jakarta.platform + jakarta.jakartaee-api + 10.0.0 + provided + + + + + org.eclipse.microprofile + microprofile + 6.1 + pom + provided + + + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + test + + + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + +... + +---- + +Below is the list of essential dependencies you need to add to your Maven _pom.xml_ for a MicroProfile project: + +* *Lombok Dependency* - Simplifies your model by auto-generating getters, setters, constructors, and other boilerplate code. +* *Jakarta EE API Dependency* - Provides the APIs for Jakarta EE, which are often used alongside MicroProfile for enterprise Java applications. +* *MicroProfile Dependency* - This is the core MicroProfile dependency that allows you to use MicroProfile specifications in your project. +* *JUnit Jupiter API for Writing Tests* - Essential for writing unit tests for your MicroProfile services. +* *JUnit Jupiter Engine for Running Tests* - Enables the execution of JUnit tests. + +These dependencies provide a foundation for building MicroProfile applications, including aspects like model simplification with Lombok, the application of Jakarta EE APIs for building robust enterprise applications, and testing with JUnit. Remember to adjust the versions based on your project requirements and the compatibility with your MicroProfile runtime​​. + +TIP: Execute the `$ mvn validate` command. This checks the _pom.xml_ file for correctness, ensuring that all necessary configuration is present and valid. diff --git a/modules/ROOT/pages/chapter02/chapter02-02.adoc b/modules/ROOT/pages/chapter02/chapter02-02.adoc new file mode 100644 index 00000000..b1d4fb7d --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-02.adoc @@ -0,0 +1,184 @@ +== Choosing Right Modules for Your MicroProfile Application + +Choosing the right modules for your MicroProfile application is crucial for ensuring that your application is lean, maintainable, and only includes the necessary functionalities to meet its requirements. + +Before diving into MicroProfile modules, it's essential to have a clear understanding of your application's requirements. Consider aspects such as configuration needs, security, health checks, data metrics, fault tolerance, and the need for distributed tracing. Mapping out these requirements will guide you in selecting the most relevant MicroProfile specifications. MicroProfile provides a selection of APIs that you can choose from based on the specific needs of your application. However, with the variety of specifications available, it's important to understand which ones best fit your project's needs. + +This section aims to help you make informed decisions about which MicroProfile modules to use. + +=== Use the Entire MicroProfile Dependency + +If you're beginning a new MicroProfile-based project and are unsure which specifications you will need, starting with the entire MicroProfile dependency can give you immediate access to the full suite of MicroProfile APIs. This approach allows you to explore and experiment with different specifications without modifying your pom.xml to add or remove dependencies frequently. + +For projects that aim to leverage a wide range of MicroProfile specifications, including advanced features like telemetry, metrics, and fault tolerance, including the entire MicroProfile 6.1 dependency ensures that you have all the necessary APIs at your disposal. This approach simplifies dependency management, especially for complex applications. + +*Maven* + +[source, xml] +---- + + + + org.eclipse.microprofile + microprofile + 6.1 + pom + provided + +---- + +*Gradle* +[source, xml] +---- +dependencies { + compileOnly 'org.eclipse.microprofile:microprofile:6.1' +} +---- + +=== Use Individual MicroProfile Specification Dependencies + +For applications where size and startup time are critical (e.g., serverless functions, microservices with stringent resource constraints), including only the necessary MicroProfile specifications can help minimize the application's footprint. This selective approach ensures that your application includes only what it needs, potentially reducing memory usage and startup time. + +To prevent potential conflicts or security issues associated with unused dependencies, it's prudent to include only the specifications your application directly uses. This practice follows the principle of minimalism in software design, reducing the surface area for bugs and vulnerabilities. + +The list below is provided to help you select the appropriate modules for your MicroProfile application: + +* *MicroProfile Config* provides a way to fetch configurations from various sources dynamically. You should use this dependency in your microservices if they require external configuration or need to be run in different environments without requiring repackaging. + +*Maven* + +[source, xml] +---- + + org.eclipse.microprofile.config + microprofile-config-api + 3.1 + +---- + + +* *MicroProfile Health* allows you to define health endpoints easily. If you're deploying your application in a environment where the service needs to report its health status. + +*Maven* + +[source, xml] +---- + + org.eclipse.microprofile.health + microprofile-health-api + 4.0.1 + +---- + + + +* *MicroProfile Metrics* offers a way to generate various metrics from your application, which can be consumed by monitoring tools. You should use this dependency in your microservices if you need to monitor the performance of your application. + +*Maven* + +[source, xml] +---- + + org.eclipse.microprofile.metrics + microprofile-metrics-api + 5.1.0 + +---- + + +* *MicroProfile Fault Tolerance* helps applications in implementing patterns like timeout, retry, bulkhead, circuit breaker, and fallback. Applications requiring resilience and reliability, especially those facing network latency or failure in microservices environments, will benefit from it. + +[source, xml] +---- + + org.eclipse.microprofile.fault-tolerance + microprofile-fault-tolerance-api + 4.0.2 + +---- + +* *MicroProfile JWT Authentication* provides a method for using JWT tokens for securing your microservices, especially where propagation of identity and authentication information is needed across services. + +[source, xml] +---- + + org.eclipse.microprofile.jwt + microprofile-jwt-auth-api + 2.1 + +---- + + +* *MicroProfile OpenAPI* offers tools for generating OpenAPI descriptions of your endpoints automatically for documenting your REST APIs. + +*Maven* + +[source, xml] +---- + + org.eclipse.microprofile.openapi + microprofile-openapi-api + 3.1.1 + +---- + +* *MicroProfile Rest Client* simplifies calling RESTful services over HTTP for type-safe invocations of HTTP services for type-safe invocations of HTTP services. + +*Maven* + +[source, xml] +---- + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + 3.0 + +---- + +* *MicroProfile Telemetry* integrates OpenTelemetry for distributed tracing For applications that need to trace requests across microservices to diagnose and monitor. + +*Maven* + +[source, xml] +---- + + + + + io.opentelemetry + opentelemetry-bom + 1.29.0 + pom + import + + + + + + io.opentelemetry + opentelemetry-api + + + +---- + +* *Jakarta EE Core Profile* dependency provides the API set included in the Jakarta EE 10 Core Profile, which is optimized for developing microservices and cloud-native Java applications with a reduced set of specifications for a lighter runtime footprint. + +*Maven* + +[source, xml] +---- + + + + jakarta.platform + jakarta.jakartaee-api + 10.0.0 + provided + + +---- + +For rapidly evolving projects or those in the exploratory phase, starting with the full MicroProfile dependency might be advantageous. However, for production applications with well-defined requirements, opting for individual specifications can lead to more efficient and maintainable solutions. + +When choosing MicroProfile modules, start with the minimal set that meets your application's core requirements. You can always integrate additional specifications as your application evolves. This approach keeps your application lightweight and focused on its primary functionalities, improving maintainability and performance. Always consider the compatibility between different versions of MicroProfile and your runtime environment to ensure seamless integration and deployment. diff --git a/modules/ROOT/pages/chapter02/chapter02-03.adoc b/modules/ROOT/pages/chapter02/chapter02-03.adoc new file mode 100644 index 00000000..5f0b1b19 --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-03.adoc @@ -0,0 +1,321 @@ +== Developing a RESTful Web Service + +*Web Services* are very popular nowadays because they allow for building decoupled systems +– services can communicate with each other without the knowledge of each other’s implementation details. +There are many different ways to design and implement web services. One popular way is to use the Representational State Transfer (REST) +architecture. A Jakarta RESTful Webservice is a web service that uses the *Representational State Transfer (REST)* architecture. +This type of web service makes it easy to build modern, scalable web applications. The REST architecture is based on the principle that +all data and functionality should be accessed through a uniform interface. This makes it easy to develop, test, and deploy web +applications. + +To understand this better, let’s create a simple RESTful service to manage a list of products for our sample application, +the MicroProfile ecommerce store. This RESTful API will allow client applications to access the product information stored as +resources on the server. For example, let’s say you have a product catalog that you want to make available as a web service. +With REST, you would create a URL that represents the resources (products) in your catalog. When a client (such as a web browser) +requests this URL, the server would return a list of products in JSON format. + +=== Creating an Entity class + +An Entity class represents a specific object, in our case a product. It contains the product's details id and name, +and other properties like price, and quantity. To implement an entity class first, you need to create a `Product` class, as below: + +[source, java] +---- +// Product.java +package io.microprofile.tutorial.store.product.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Product { + private Long id; + private String name; + private String description; + private Double price; +} +---- + +Explanation: + +* The `Product` class is a Plain Old Java Object (POJO). It has an `id`, `name`, `description` and `price` property. The `id` property is of type `Long`, The `name` and `description` properties are of type `String`. The `price` property is of type `Double`. + +* `@Data` annotation will generate constructors, getters, and setters for all fields. By doing this, +you enable the Jackson library to convert your Java objects to JSON and vice versa. All properties must be of `Object` type as well. +Jackson cannot work with primitive types because they cannot be `null`. + +* `@AllArgsConstructor` generates a constructor with one argument for each field in the class. +This is useful for instantiating objects with all their fields initialized. + +* `@NoArgsConstructor` generates a default constructor +for the class. + +=== Creating a Resource class + +A resource class represents a collection of related resources. It includes methods for creating, updating, deleting, and retrieving +(CRUD) operations on the resources. Let us now create a `ProductResource` class with a `getProducts()` method to return a list of +`Product` objects. + +[source, java] +---- +// ProductResource.java +package io.microprofile.tutorial.store.product.resource; + +import java.util.ArrayList; +import java.util.List; + +import io.microprofile.tutorial.store.product.entity.Product; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/products") +@ApplicationScoped +public class ProductResource { + + private List products = new ArrayList<>(); + + public ProductResource() { + // Initialize the list with some sample products + products.add(new Product(Long.valueOf(1L), "iPhone", "Apple iPhone 15", Double.valueOf(999.99))); + products.add(new Product(Long.valueOf(2L), "MacBook", "Apple MacBook Air", Double.valueOf(1299.99))); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List getProducts() { + return products; + } +} +---- + +Explanation: + +* The `ProductResource` is annotated with `@ApplicationScoped`. This will ensure that only one instance of this class available when the +application is running. + +* The `ProductResource` class has a `getProducts()` method, which returns a list of products. This method is annotated with the `@GET` annotation, which maps this method to the `GET` HTTP method. + +* The `@Produces` annotation tells the server that this method produces JSON content. This will return the following JSON response when we make a `GET` request to the `/api/products` endpoint. + +* RESTful web services can produce and consume many different media types, including JSON, XML, and HTML. + +* Annotations specify the media type that a method can consume or produce. For example, if a method is annotated with +`@Produces(MediaType.APPLICATION_JSON)` it can produce JSON. + +=== Creating an Applicaiton class + +Create a class named `ProductRestApplication` as per the code below: + +[source, java] +---- +// ProductRestApplication.java +package io.microprofile.tutorial.store.product; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +@ApplicationPath("/api") +public class ProductRestApplication extends Application{ + +} +---- + +Explanation: + +* The annotation `@ApplicationPath("/api")` specifies that any RESTful resources registered within this application will be accessed +under the base path `/api`. For example, if you have a resource class named `ProductResource` mapped to the path `/products`, it would be accessible at `/api/products`. + +=== Building Your Application + +You may build the application using the following commands from your project’s root directory: + +[source, bash] +---- +$ mvn compile +---- + +The above command will compile your project’s source code. + + +[source, bash] +---- +$ mvn test +---- + +The above command will run the test using a unit testing framework. These test should not require the code to be packaged and deployed. + + +[source, bash] +---- +$mvn package +---- + +The above command will create a deployment package. + +=== Deploying your microservices + +This section guides you through deploying your newly created product microservice to a runtime environment. Below are some of the general considerations: + +==== General Considerations: +* Runtime Compatibility: Ensure your chosen runtime supports the MicroProfile version used in your project. +* Packaging: Decide on a packaging format (e.g., WAR file, Docker image). +* Configuration: Review and adjust any runtime configuration necessary for your service. +* Deployment Tools: Leverage runtime-specific tools or commands for deployment. + +==== Deployment Options +You can then deploy this application on a MicroProfile compatible server and access the web service at +`http://localhost://api/products`. Replace `` with the port number on which the web server or +application server is listening. The `` is a placeholder for the context root of the web application. +The context root is part of the URL path that identifies the base path for the application on the web server. + +Below are the steps for popular options. Specific steps will depend on your chosen runtime. + +*Open Liberty* + +Package your application as a WAR file using Maven or Gradle by adding the packaging tag in `pom.xml`. + +[source, xml] +---- +io.microprofile.tutorial +mp-ecomm-store +1.0-SNAPSHOT +war +---- + +Add a server configuration file at the location /main/liberty/config/server.xml with the content as below: + +[source, xml] +---- + + + restfulWS-3.1 + jsonb-3.0 + + + + + +---- + +Add the Open Liberty configuration in the pom.xml as below: + +[source, xml] +---- + + UTF-8 + UTF-8 + 17 + 17 + + 9080 + 9443 + mp-ecomm-store + +---- +Add the Open Liberty build plugin in the pom.xml as below: + +[source, xml] +---- + + ${project.artifactId} + + + org.apache.maven.plugins + maven-war-plugin + 3.3.2 + + + io.openliberty.tools + liberty-maven-plugin + 3.8.2 + + productServer + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0 + + + ${liberty.var.default.http.port} + ${liberty.var.app.context.root} + + + + + +---- + +=== Running Your Application + +Refer to your runtime’s documentation for instructions on running your MicroProfile application. +For example, Consult the Open Liberty documentation for detailed instructions: +link:https://openliberty.io/docs/latest/microprofile.html[MicroProfile - Open Liberty Docs] +Finally, use the following command from the command line or terminal to run the application on Liberty server. + +[source, bash] +---- +$ mvn liberty:run +---- + +You can also run the following command to start the liberty server in development mode. + +[source, bash] +---- +$ mvn liberty:dev +---- + +Assuming your server is running on `http://localhost:9080/`, you can access your service at: +`http://localhost:9080/mp-ecomm-store/api/products`. + +To call this RESTful web service, you can enter the URL in your browser. The response is an array of JSON objects. +Each object has an id, name, description and price property. Please note only GET methods can be tested with browsers. +The response should be: + +[source, json] +---- +[{"description":"Apple iPhone 15","id":1,"name":"iPhone","price":999.99},{"description":"Apple MacBook Air","id":2,"name":"MacBook","price":1299.99}] +---- + +This uses an in-memory list; In a real application you should integrate a database (via Jakarta Persistence API). We will be learning about this in the next chapter. + +*Quarkus* + +* Build your application as a native executable or Docker image. +* Run the generated executable or deploy the Docker image to a container platform. +* Refer to the Quarkus documentation for deployment guides: link:https://quarkus.io/guides/getting-started[Creating your first application - Quarkus] + +*Payara Micro* + +* Package your application as a WAR file. +* Deploy the WAR to a Payara Micro server instance. +* See the Payara Micro documentation for specific instructions: link:https://www.payara.fish/learn/getting-started-with-payara-micro/[Getting Started with Payara Micro] + +*WildFly* + +* Package your application as a WAR file. +* Deploy the WAR to a WildFly server instance. +* Refer to the WildFly documentation for deployment details: link:https://docs.wildfly.org/31/Developer_Guide.html[WildFly Developer Guide] + +*Helidon* + +* Choose between Helidon SE (native packaging) or Helidon MP (WAR packaging). +* Build your application using Gradle. +* Follow the relevant Helidon documentation for deployment steps: link:https://helidon.io/docs/v4/about/prerequisites[Helidon - Getting Started] + +*TomEE* + +* Package your application as a WAR file. +* Deploy the WAR file to the TomEE server instance. +* Refer to the TomEE documentation for instructions: link:https://tomee.apache.org/latest/examples/serverless-tomee-microprofile.html[Serverless TomEE MicroProfile] + +==== Additional Considerations: +* Containerization: Consider using containerization technologies like Docker and Kubernetes for portability and scalability. +* Cloud Deployment: Explore cloud platforms like AWS, Azure, or GCP. diff --git a/modules/ROOT/pages/chapter02/chapter02-04.adoc b/modules/ROOT/pages/chapter02/chapter02-04.adoc new file mode 100644 index 00000000..54a43f99 --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-04.adoc @@ -0,0 +1,88 @@ +== Testing Your Microservice + +Testing your microservice is critical for ensuring the reliability and robustness of your microservice. Maven, being a powerful project build management tool, simplifies this process by automating the test execution. +To create tests for your microservice, start by creating a class called `ProductResourceTest`, which contains unit tests for the `ProductResource` class as below: + +[source, java] +---- +// ProductResourceTest.java +package io.microprofile.tutorial.store.product.resource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.microprofile.tutorial.store.product.entity.Product; + +public class ProductResourceTest { + private ProductResource productResource; + + @BeforeEach + void setUp() { + productResource = new ProductResource(); + } + + @AfterEach + void tearDown() { + productResource = null; + } + + @Test + void testGetProducts() { + List products = productResource.getProducts(); + + assertNotNull(products); + assertEquals(2, products.size()); + } +} +---- + +Explanation: + +Below are the assertions to test the `getProducts()` method of `ProductService`: +* The `assertNotNull(products)`; assertion checks that products are not `null`. It ensures the method returns a list, even if it’s empty. +* The `assertEquals(2, products.size())`; assertion verifies that the list contains two products. + +=== Running Unit Tests with Maven + +To run the unit tests defined in ProductResourceTest, follow these steps: + +* Open a Terminal or Command Prompt: Navigate to the root directory of your project where the pom.xml file is located. This file contains the Maven project definition, including dependencies and test configurations. + +* Execute the Maven Test Command: Enter the following command to initiate the execution of the unit tests: + +[source, shell] +---- +$ mvn test +---- + +This command tells Maven to execute the test phase of the build lifecycle. Maven will compile the test source code, execute the test cases, and provide a summary of the test execution results. + +* Review Test Results: After running the tests, Maven displays the results in the terminal. Look for the Tests run:, Failures:, and Errors: summaries to assess the outcome. For the ProductResourceTest class, ensure that the test methods execute successfully and meet the expected assertions: + +* Addressing Failures: If any tests fail, Maven will highlight these failures in the output. Use this information to identify and fix issues in your code. Review the failing tests' output for details on the assertion failures and adjust your microservice implementation accordingly. + +* Rerun Tests: After making any necessary changes to your microservice code, rerun the tests using the mvn test command to verify that all issues have been resolved and that your microservice behaves as expected. + +By following these steps, you can leverage Maven to efficiently run and manage unit tests for your microservice, ensuring its functionality and reliability before deployment. + +== Next Steps + +Now that you have a basic MicroProfile service, consider exploring further: + +* Adding configuration with MicroProfile Config +* Implementing health checks using MicroProfile Health +* Enhancing your service with MicroProfile Fault Tolerance + +=== Resources + +* MicroProfile Official Website: https://microprofile.io/ +* MicroProfile GitHub Repository: https://github.com/eclipse/microprofile +* MicroProfile Documentation and Guides: link:https://microprofile.io/documentation/[MicroProfile Official documentation] + +After completing this chapter, you should have a basic understanding of MicroProfile and how to start building microservices with it. Continue exploring the specifications and capabilities of MicroProfile to fully leverage its power in your microservices architecture. diff --git a/modules/ROOT/pages/chapter02/chapter02-05.adoc b/modules/ROOT/pages/chapter02/chapter02-05.adoc new file mode 100644 index 00000000..f857fe1e --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-05.adoc @@ -0,0 +1,97 @@ +== Package Structure + +The Table below provides an overview of the package structure and their purposes within a typical Java-based +microservices architecture. + +[options="header"] +|=== +|Package |Description + +|dto +|Data Transfer Objects (DTOs) are used to transfer data between processes, such as from your service to a REST endpoint. They often mirror entity classes but can be tailored to the needs of the client to avoid over-fetching or under-fetching data. + +|entity +|Entity classes represent the domain model and are typically mapped to database tables. These are the core classes that represent the business data and are often used with ORM tools like JPA. + +|repository +|Interfaces in this package abstract the data layer, making it easier to perform CRUD operations without dealing with database intricacies directly. This follows the Repository pattern. Data access layer, interacting with databases or other storage mechanisms (e.g., ProductRepository, CustomerRepository). + +|service +|Service classes contain the core business logic. They interact with repositories to fetch and persist data and perform operations specific to the business requirements. (e.g., ProductService, OrderService, InventoryService). + +|resource +|REST resource classes (sometimes called controllers in other frameworks) are the entry points for HTTP requests. They interact with service classes to process these requests. Interfaces defining endpoints for REST services (e.g., ProductResource, ShoppingCartResource). + +|common +|This package contains classes and interfaces that are shared across different microservices, such as utility classes, common configuration, exception handling, and security-related classes. + +|client +|For microservices to communicate with each other, they often use HTTP clients. This package contains interfaces or classes annotated for use with MicroProfile Rest Client or similar, facilitating easy communication between your services. + +|config +|Configuration classes for MicroProfile Config. + +|exception +|Custom exceptions for error handling (e.g., ProductNotFoundException, PaymentFailedException). + +|util +|Helper and utility classes. +|=== + + +*Base Package*: `io.microprofile.tutorial.store` + +[source, plaintext] +io.microprofile.tutorial.store +├── catalog +│ ├── resource +│ ├── config +│ ├── exception +│ ├── entity +│ ├── repository +│ ├── service +│ └── util +├── cart +│ ├── resource +│ ├── entity +│ ├── service +│ ├── repository +│ ├── client +│ ├── exception +│ └── util +├── user +│ ├── resource +│ ├── entity +│ ├── service +│ ├── repository +│ ├── exception +│ └── util +├── inventory +│ ├── resource +│ ├── entity +│ ├── service +│ ├── repository +│ ├── exception +│ └── util +├── order +│ ├── resource +│ ├── entity +│ ├── service +│ ├── repository +│ ├── exception +│ └── util +├── payment +│ ├── resource +│ ├── entity +│ ├── service +│ ├── repository +│ ├── exception +│ └── util +└── shipment + ├── resource + ├── entity + ├── service + ├── repository + ├── exception + └── util +---- diff --git a/modules/ROOT/pages/chapter02/chapter02-06.adoc b/modules/ROOT/pages/chapter02/chapter02-06.adoc new file mode 100644 index 00000000..f0825f67 --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02-06.adoc @@ -0,0 +1,23 @@ +[glossary] +== Glossary + +Java Development Kit (JDK):: +A software development environment used for developing Java applications. It includes the Java Runtime Environment (JRE), an interpreter/loader (Java), a compiler (javac), an archiver (jar), a documentation generator (Javadoc), and other tools needed in Java development. + +Integrated Development Environment (IDE):: +A software application that provides comprehensive facilities to computer programmers for software development. Examples include Eclipse, IntelliJ IDEA, NetBeans, and Visual Studio Code. + +RESTful Service:: +A web service implementing REST (Representational State Transfer) principles, providing interoperability between computer systems on the internet. + +Runtime Environment:: +The environment in which programs are executed. It includes everything your application needs to run in production, such as an operating system, a runtime (like JVM for Java applications), libraries, and environment variables. + +JUnit:: +A unit testing framework for Java, used to write and run repeatable tests. + +Containerization:: +A lightweight alternative to full machine virtualization that involves encapsulating an application in a container with its own operating environment. + +Cloud Deployment:: +Deploying applications in cloud environments, leveraging cloud resources like compute instances, storage, and networking capabilities. diff --git a/modules/ROOT/pages/chapter02/chapter02.adoc b/modules/ROOT/pages/chapter02/chapter02.adoc new file mode 100644 index 00000000..e656be09 --- /dev/null +++ b/modules/ROOT/pages/chapter02/chapter02.adoc @@ -0,0 +1,7 @@ +include::chapter02-00.adoc[] +include::chapter02-01.adoc[] +include::chapter02-02.adoc[] +include::chapter02-03.adoc[] +include::chapter02-04.adoc[] +include::chapter02-05.adoc[] +include::chapter02-06.adoc[] \ No newline at end of file diff --git a/chapter03/chapter03.adoc b/modules/ROOT/pages/chapter03/chapter03.adoc similarity index 97% rename from chapter03/chapter03.adoc rename to modules/ROOT/pages/chapter03/chapter03.adoc index 0298fcf2..7b0fb366 100644 --- a/chapter03/chapter03.adoc +++ b/modules/ROOT/pages/chapter03/chapter03.adoc @@ -1,4 +1,6 @@ -= Chapter 03: Jakarta EE 10 Core Profile += Jakarta EE 10 Core Profile +:doctype: book +:id: chapter03 == Introduction @@ -42,12 +44,9 @@ Let's delve deeper into some of the specifications included in the Jakarta Core This specification simplifies the code by reducing the need for external configuration files and making the intentions behind code clear. Annotations are extensively used across various Jakarta EE specifications. -==== Key Features of Jakarta Annotation - +==== Key Features * *Simplification of Configuration*: Annotations reduce the need for XML configuration files, making the setup more straightforward and less error-prone. - * *Enhanced Readability and Maintenance*: Code decorated with annotations is easier to read and maintain, as the configuration is co-located with the code it configures. - * *Wide Adoption*: Used across the Jakarta EE platform for a variety of purposes, including dependency injection, defining REST endpoints, and configuring beans. === Jakarta Contexts and Dependency Injection (CDI) - CDI Lite @@ -66,7 +65,7 @@ NOTE: The link:https://jakartaee.github.io/jakartaee-documentation/jakartaee-tut === Jakarta Interceptors -Jakarta Interceptors allow developers to define methods that intercept business method invocations and lifecycle events on Jakarta EE components. This is particularly useful for implementing cross-cutting concerns such as logging, transactions, security, and more, without cluttering business logic.. +Jakarta Interceptors allow developers to define methods that intercept business method invocations and lifecycle events on Jakarta EE components. This is particularly useful for implementing cross-cutting concerns such as logging, transactions, security, and more, without cluttering business logic. ==== Key Features of Jakarta Interceptors @@ -138,13 +137,11 @@ Jakarta Annotations is used for defining RESTful services and injecting dependen ---- package io.microprofile.tutorial.store.product.entity; - import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.GeneratedValue; import jakarta.validation.constraints.NotNull; - @Entity @Table(name = "Product") @NamedQuery(name = "Product.findAllProducts", query = "SELECT p FROM Product p") @@ -202,6 +199,14 @@ public class ProductRepository { @PersistenceContext(unitName = "product-unit") private EntityManager em; + private List products = new ArrayList<>(); + + public ProductRepository() { + // Initialize the list with some sample products + products.add(new Product(1L, "iPhone", "Apple iPhone 15", 999.99)); + products.add(new Product(2L, "MacBook", "Apple MacBook Air", 1299.0)); + } + public void createProduct(Product product) { em.persist(product); } @@ -273,25 +278,24 @@ import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; - @Path("/products") @ApplicationScoped public class ProductResource { + private static final Logger LOGGER = Logger.getLogger(ProductResource.class.getName()); + + // ... @Inject private ProductRepository productRepository; - @GET @Produces(MediaType.APPLICATION_JSON) - @Transactional - public List getProducts() { - // Return a list of products - return productRepository.findAllProducts(); + public Response getAllProducts() { + LOGGER.info("Fetching all products"); + return Response.ok(products).build(); } - // Additional endpoint methods } ---- @@ -448,7 +452,7 @@ Explanation: * If a product with the same id is not found, the method returns a 404 (Not Found) error. Finally, the method returns a `Response` object with a status code of 204 (No Content) and a message indicating that an existing product has been updated. -===== Verifying the PUT request +==== Verifying the PUT request To test the PUT request, you can use the following cURL command. diff --git a/chapter04/chapter04.adoc b/modules/ROOT/pages/chapter04/chapter04.adoc similarity index 96% rename from chapter04/chapter04.adoc rename to modules/ROOT/pages/chapter04/chapter04.adoc index b0b7442b..a25f56a0 100644 --- a/chapter04/chapter04.adoc +++ b/modules/ROOT/pages/chapter04/chapter04.adoc @@ -1,19 +1,21 @@ -== Chapter 4: MicroProfile OpenAPI += MicroProfile OpenAPI -=== Introduction +:imagesdir: ../../assets/images + +== Introduction In the previous chapter, we saw how RESTful APIs facilitate language-agnostic access to web services from diverse environments. However, a clear and comprehensive contract is required to ensure seamless integration between clients and services. This need for a well-defined API contract has led to the adoption of the OpenAPI specification. This chapter will explore the primary features of MicroProfile OpenAPI, demonstrate how to integrate it into your MicroProfile applications, and show you how to annotate your RESTful services to produce rich documentation that adheres to the OpenAPI specification. Furthermore, we will introduce the OpenAPI UI, a visual interface allowing developers and stakeholders to interact with and visualize the documented APIs, enhancing understanding and facilitating integration. -=== Topics to be covered: +== Topics to be covered: - Introduction to MicroProfile OpenAPI - API Specification using MicroProfile Open API - Generating API Documentation - Documenting Authentication and Authorization Requirements - Exploring the APIs using Swagger UI -=== OpenAPI Specification +== OpenAPI Specification The Open API Specification (OAS), formerly Swagger specification, is a technical specification that allows REST API providers to describe and publish their APIs using a format that various tools can consume. It defines a standard, language-agnostic interface to RESTful APIs, making it easy for third-party tools to generate documentation, client SDKs, and a range of tools that promote the seamless consumption of RESTful APIs. @@ -40,7 +42,7 @@ The OpenAPI Specification fuels a rich ecosystem of tools that automate and supp * *API Testing*: Testing frameworks can leverage the specification to design robust tests. * *API Mocking*: Simplifies mocking APIs for testing and development purposes. -=== Generating OpenAPI documents +== Generating OpenAPI documents There are multiple ways in which you can generate OpenAPI documents. The most common way is to use annotations. This only requires augmenting your Jakarta Restful Web Services annotations with OpenAPI annotations. @@ -66,19 +68,23 @@ Below is an illustrative example of how you might annotate a method in the `Prod [source, java] ---- import org.eclipse.microprofile.openapi.annotations.Operation; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import java.util.List; +@ApplicationScoped @Path("/products") +@Tag(name = "Product Resource", description = "CRUD operations for products") public class ProductResource { + //... + @GET @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "List all products", description = "Retrieves a list of all available products") @@ -87,7 +93,7 @@ public class ProductResource { responseCode = "200", description = "Successful, list of products found", content = @Content(mediaType = "application/json", - schema = @Schema(implementation = Product.class, type = SchemaType.ARRAY)) + schema = @Schema(implementation = Product.class)) ), @APIResponse( responseCode = "400", @@ -95,7 +101,7 @@ public class ProductResource { content = @Content(mediaType = "application/json") ) }) - public List getProducts() { + public List getAllProducts() { // Method implementation } } @@ -123,7 +129,7 @@ This property tells MicroProfile OpenAPI to scan our classes for annotations and Now that we have configured MicroProfile OpenAPI, we can build and run our application. -=== How to view the generated documentation +== How to view the generated documentation To view the generated documentation, we can use the OpenAPI UI tool. The Open API UI tool is a web-based tool that can be used to view the documentation for a REST API. @@ -137,7 +143,7 @@ Replace `` with the actual port used by your runtime, for e.g. 9080 which The `/openapi` endpoint is used to get information about the OpenAPI specification generated from the comments in the source code annotations. It returns information in YAML format. -When we access the `http://localhost:9080/openapi` URL, we should see the API documentation that was generated by MicroProfile OpenAPI: +When we access the `http://localhost:5050/openapi` URL, we should see the API documentation that was generated by MicroProfile OpenAPI: [source, yaml] ---- @@ -230,7 +236,7 @@ As we can see, MicroProfile OpenAPI has generated API documentation for our reso MicroProfile OpenAPI allows developers to produce these specifications directly from their codebase, leveraging annotations and/or providing OpenAPI documents statically. This direct generation ensures that the API documentation is always up to date with the code. -=== Exploring the APIs using Swagger UI +== Exploring the APIs using Swagger UI To open Swagger UI for the API documentation generated using MicroProfile OpenAPI, you will need to deploy your application to a server that supports MicroProfile, such as Open Liberty, WildFly, Quarkus, or Payara Micro. These servers automatically generate the OpenAPI documentation for your RESTful services based on the annotations in your code. @@ -244,7 +250,7 @@ Swagger UI is then used to render this documentation in a user-friendly web inte :figure-caption: Swagger UI .Swagger UI -image::../images/figure4-1.png[Swagger UI] +image::figure4-1.png[MicroProfile OpenAPI] == Annotations @@ -314,4 +320,4 @@ All of these annotations are defined in the org.eclipse.microprofile.openapi.ann == Summary -By integrating the MicroProfile OpenAPI, developers can generate detailed, OpenAPI-compliant documentation automatically, fostering better understanding and interaction among services. By annotating `ProductResource` class, we generated API documentation as per Open API specification. This will ensure the services are readily discoverable, understandable, and usable, thereby accelerating development cycles and fostering a more robust and collaborative developer ecosystem. +By integrating the MicroProfile OpenAPI, developers can generate detailed, OpenAPI-compliant documentation automatically, fostering better understanding and interaction among services. By annotating `ProductResource` class, we generated API documentation as per Open API specification. This will ensure the services are readily discoverable, understandable, and usable, thereby accelerating development cycles and fostering a more robust and collaborative developer ecosystem. diff --git a/chapter05/chapter05.adoc b/modules/ROOT/pages/chapter05/chapter05.adoc similarity index 99% rename from chapter05/chapter05.adoc rename to modules/ROOT/pages/chapter05/chapter05.adoc index 8f7cf1fe..07f779d4 100644 --- a/chapter05/chapter05.adoc +++ b/modules/ROOT/pages/chapter05/chapter05.adoc @@ -1,4 +1,4 @@ -= Chapter 5: MicroProfile Configuration += MicroProfile Configuration This chapter focuses on MicroProfile Configuration, a key feature that allows developers to externalize configuration properties from their code. You can adapt configuration parameters to different environments (development, testing, production) without altering the core code. It provides flexibility and adaptability for microservices in different environments. diff --git a/chapter06/chapter06.adoc b/modules/ROOT/pages/chapter06/chapter06.adoc similarity index 97% rename from chapter06/chapter06.adoc rename to modules/ROOT/pages/chapter06/chapter06.adoc index 76a9a89d..1ff251d8 100644 --- a/chapter06/chapter06.adoc +++ b/modules/ROOT/pages/chapter06/chapter06.adoc @@ -1,13 +1,13 @@ -== Chapter 6: MicroProfile Health += MicroProfile Health -=== Introduction +== Introduction This chapter provides an in-depth exploration of MicroProfile Health, a critical component for ensuring the reliability and availability of microservices. This specification aims to enhance the observability of microservices in a cloud environment where automatic scaling, failover, and recovery are essential for maintaining service availability and reliability. In this chapter, we will learn about different types of health checks and standard health indicators provided by MicroProfile. -=== Topics to be covered: +== Topics to be covered: - Overview of MicroProfile Health - Key Concepts @@ -19,7 +19,7 @@ chapter, we will learn about different types of health checks and standard healt - Kubernetes Probe Configuration - Best Practices for Effective Health Checks -=== Overview of MicroProfile Health +== Overview of MicroProfile Health The MicroProfile Health specification offers a standardized mechanism for microservices to report their health status. In the context of microservices, "health" refers to the ability of a microservice to perform its functions correctly and efficiently. @@ -29,14 +29,14 @@ other actions to maintain overall system reliability. Let’s delve into the essentials of MicroProfile Health, its importance, and how it works. -=== Key Concepts +== Key Concepts At its core, the MicroProfile Health specification defines a mechanism for microservices to report their health status via HTTP. These health checks can be used by external systems to verify the operational status of the services. This is crucial in modern cloud environments where automated processes continuously monitor service health, initiate failover procedures, and manage load balancing to ensure high availability and reliability. -==== Health Check +=== Health Check A *health check* is a test that can be used to determine the health of an application or service. This mechanism is implemented via standard HTTP endpoints that respond with the health status of the service. These endpoints are typically exposed at predefined paths, @@ -44,26 +44,26 @@ such as `/health`, `/health/live` (for liveness), `/health/ready` (for readiness communicated through a simple JSON format, which can be easily interpreted by humans and machines. Applications servers that support MicroProfile may offer built-in mechanisms or simplified configurations to define such health checks. -=== Types of Health Checks +== Types of Health Checks MicroProfile Health Check defines three main types of health checks, each with its own annotation to indicate the MicroProfile Health runtime about the type of check being performed, allowing it to execute and report health check responses appropriately. These are: -==== Liveness Checks +=== Liveness Checks Liveness checks help to determine if a microservice is in a state where it can perform its functions correctly. A failing liveness check suggests that the microservice is in a broken state, and the only way to recover might be to restart the microservice. This type of health check is crucial for detecting deadlocks, infinite loops, or any conditions that render the microservice unresponsive or dysfunctional. Liveness checks are annotated with `@Liveness`. -==== Readiness Checks +=== Readiness Checks Readiness checks are used to determine if a microservice is ready to process requests. If a readiness check fails, it indicates that the microservice should not receive any inbound requests because it’s not ready to handle them properly. This can be due to the application still initializing, waiting for dependencies, or any other condition that would prevent it from correctly processing incoming requests. Readiness checks are annotated with `@Readiness`. -==== Startup Checks +=== Startup Checks Startup checks are designed for verifying the microservice’s health immediately after it has started. This type of check is useful for applications that require additional initialization time or need to perform certain actions before they are ready to serve requests. @@ -73,7 +73,7 @@ startup conditions are fulfilled. This ensures that readiness and liveness probe adequate time to complete its initialization processes, such as loading configurations, establishing database connections, or performing necessary pre-service tasks.These checks are annotated with `@Startup`. -=== Exposing Health Checks +== Exposing Health Checks Health checks are exposed via HTTP endpoints automatically without additional configuration needed from the developer’s side. The runtime environment provides these endpoints: @@ -86,7 +86,7 @@ environment provides these endpoints: These endpoints return a JSON object containing the overall status (UP or DOWN) and individual health check responses, including their names, statuses, and optional data. -==== Example JSON Response +=== Example JSON Response For example a `LivenessCheck`, if accessed via `/health/live`, the JSON response might look something like this when the service is healthy: @@ -109,16 +109,16 @@ the health status (UP or DOWN) and optionally includes additional details. Imple that services are only used when they are in a healthy state and can correctly process requests. This enhances the overall reliability and maintainability of applications. -==== Standard Health Check +=== Standard Health Check Applications can implement multiple health checks of each kind. The overall health status reported by the application is a logical AND of all individual health checks. A special endpoint `/health` aggregates the results from all three types of checks. -==== Implementing and Exposing Health Check +=== Implementing and Exposing Health Check To implement health checks for microservices using MicroProfile Health, you would generally follow a pattern to define health check procedures that align with the services' operational characteristics. The Health Check API allows us to expose information about the health of our application. This information can be used by load balancers and other tools to determine if an application is healthy. -==== The `HealthCheck` interface +=== The `HealthCheck` interface The `HealthCheck` functional interface uses CDI beans with annotations (`@Liveness`, `@Readiness`, and, `@Startup`) to mark a class as a health checker for liveness, readiness and startup. They are automatically discovered and registered by the runtime. Implementations of this interface are expected to be provided by applications. @@ -136,7 +136,7 @@ public interface HealthCheck { You can check out the actual code here - https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheck.java -==== The `HealthCheckResponse` class +=== The `HealthCheckResponse` class The `HealthCheckResponse` class is used to represent the result of a health check invocation. It contains information about the health check, such as name, state (up or down), and data that can be used for troubleshooting. @@ -182,7 +182,7 @@ public class HealthCheckResponse { The provided code snippet offers a conceptual and simplified implementation of the `HealthCheckResponse` class to illustrate how health check responses can be structured within the MicroProfile Health framework. To view the actual `HealthCheckResponse` class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponse.java -==== The `HealthCheckResponseBuilder` class +=== The `HealthCheckResponseBuilder` class The `HealthCheckResponseBuilder` abstract class provides a fluent API for constructing instances of `HealthCheckResponse`. This means you can chain method calls to set various properties of the response in a single statement, improving code readability and maintainability. @@ -220,7 +220,7 @@ public abstract class HealthCheckResponseBuilder { The above code snippet offers a conceptual and simplified definition of the `HealthCheckResponseBuilder` abstract class to illustrate how health check responses can be structured within the MicroProfile Health framework. For the actual `HealthCheckResponseBuilder` abstract class source code, please visit: https://github.com/eclipse/microprofile-health/blob/main/api/src/main/java/org/eclipse/microprofile/health/HealthCheckResponseBuilder.java -=== Steps for Implementing Health Checks +== Steps for Implementing Health Checks Below are the steps for implementing Health Checks for each of the microservices: @@ -248,7 +248,7 @@ NOTE: When implementing MicroProfile Health checks, including the MicroProfile H The health information can be used by other tools to help keep our application running well. -==== Implementing Health Checks +=== Implementing Health Checks Health checks in MicroProfile are implemented as CDI beans that implement the `HealthCheck` interface. Each health check procedure is a method that returns a `HealthCheckResponse`. You can define different types of health checks (readiness, liveness, and startup) depending on the type of check by annotating the health check class with `@Readiness`, `@Liveness`, or `@Startup`. These methods return a `HealthCheckResponse` object, which includes the health check status (UP or DOWN) and additional metadata about the health check. @@ -383,18 +383,18 @@ public class ProductServiceStartupCheck implements HealthCheck{ } ---- -=== Integration with CDI +== Integration with CDI The specification also emphasizes the importance of integrating health checks with the application’s Context and Dependency Injection (CDI) context, enabling health check procedures to be automatically discovered and invoked by the runtime. MicroProfile Health thus provides a robust and standardized way to implement health checks, facilitating the management and orchestration of microservices in a cloud environment. -=== Accessing Health Checks +== Accessing Health Checks Once defined, these health check procedures are automatically discovered and invoked by the MicroProfile Health runtime. They are accessible through standardized HTTP endpoints provided by MicroProfile Health (`/health`, `/health/live`, `/health/ready`, `/health/started`) and can be used by orchestration tools (like Kubernetes) or monitoring systems to manage and monitor the health of your microservices. This approach allows you to tailor health checks to the operational specifics of each microservice, providing a robust mechanism for observing and managing your application’s health in a cloud-native environment. -=== Kubernetes Probe Configuration +== Kubernetes Probe Configuration Integrating MicroProfile Health checks with Kubernetes probes allows you to leverage Kubernetes' native capabilities to manage the lifecycle of your containers based on their current health status. Specifically, you can map Liveness, Readiness, and Startup probes in Kubernetes to the corresponding health check types defined by the MicroProfile Health specification. @@ -434,7 +434,7 @@ In the above example, the readinessProbe is configured to make an HTTP GET reque It's important to adjust the `initialDelaySeconds`, `timeoutSeconds`, `periodSeconds`, and `failureThreshold` according to the specifics of your application to ensure that Kubernetes accurately reflects the state of your containers based on its health checks. -=== Best Practices for Effective Health Checks +== Best Practices for Effective Health Checks Here are some best practices for implementing and utilizing health checks effectively: @@ -454,7 +454,7 @@ Here are some best practices for implementing and utilizing health checks effect By following these best practices, you can effectively implement and expose health checks in your MicroProfile applications, improving observability and reliability, especially in cloud-native environments. -=== Summary +== Summary This chapter provided a comprehensive overview of MicroProfile Health, emphasizing its critical role in enhancing the observability and reliability of microservices within cloud environments. Key topics included an introduction to the MicroProfile Health specification, detailed explanations of health check types (liveness, readiness, and startup checks), and guidance on implementing, exposing, and effectively utilizing these health checks. @@ -462,4 +462,4 @@ The essence of MicroProfile Health lies in its standardized mechanism for micros Implementing health checks involves creating procedures annotated with the respective health check annotations. These procedures return a HealthCheckResponse indicating the service's health status (UP or DOWN). These checks are automatically exposed via predefined HTTP endpoints, allowing easy integration with orchestration tools like Kubernetes. -The chapter also touched on best practices for effective health checks, including defining meaningful checks, utilizing health check responses, handling failures gracefully, and securing health check endpoints. In conclusion, MicroProfile Health offers a robust framework for monitoring and managing the health of microservices, ensuring that services remain reliable and available in dynamic cloud environments. By following the guidelines and best practices outlined in this chapter, developers can effectively implement and leverage health checks to maintain the overall health of their applications. +The chapter also touched on best practices for effective health checks, including defining meaningful checks, utilizing health check responses, handling failures gracefully, and securing health check endpoints. In conclusion, MicroProfile Health offers a robust framework for monitoring and managing the health of microservices, ensuring that services remain reliable and available in dynamic cloud environments. By following the guidelines and best practices outlined in this chapter, developers can effectively implement and leverage health checks to maintain the overall health of their applications. \ No newline at end of file diff --git a/chapter07/chapter07.adoc b/modules/ROOT/pages/chapter07/chapter07.adoc similarity index 92% rename from chapter07/chapter07.adoc rename to modules/ROOT/pages/chapter07/chapter07.adoc index b7dc75f8..f505068d 100644 --- a/chapter07/chapter07.adoc +++ b/modules/ROOT/pages/chapter07/chapter07.adoc @@ -1,6 +1,6 @@ -== Chapter 7: MicroProfile Metrics += MicroProfile Metrics -=== Introduction +== Introduction This chapter provides a comprehensive and detailed overview of MicroProfile Metrics, a widely used specification for monitoring microservices. You will gain an understanding of the various types of metrics and how you can use them to monitor microservices effectively. Additionally, this chapter covers the standard metrics provided by MicroProfile and how you can leverage them to monitor various aspects of microservices. @@ -10,7 +10,7 @@ This chapter also highlights the importance of integrating monitoring solutions By the end of this chapter, you will have a deep understanding of MicroProfile Metrics and the various techniques for monitoring microservices. This chapter will equip you with the knowledge and skills to effectively monitor your microservices and ensure they perform optimally. -=== Topics to be covered: +== Topics to be covered: - Introduction to MicroProfile Metrics - Need for Metrics in Microservices @@ -22,7 +22,7 @@ By the end of this chapter, you will have a deep understanding of MicroProfile M - Instrumenting Microservices with Metrics - Creating Custom Metrics -=== Introduction to MicroProfile Metrics +== Introduction to MicroProfile Metrics It is essential to monitor your microservices to ensure smooth operations. You can monitor a microservice using two different techniques: Metrics and health checking. Health checks provide information on the health status of a service, such as whether it is up and running, while Metrics offer more detailed information on its performance, such as response times, throughput, and error rates. In the previous chapter, we discussed health checks and their importance. This chapter will cover the MicroProfile Metrics specification, which provides a standardized way of collecting and exposing performance data for Java microservices. @@ -34,7 +34,7 @@ Moreover, this specification defines a set of standard metrics that we can expos *Prometheus* is a powerful tool designed to monitor and collect metrics from your services. It provides a highly efficient time-series database system that securely stores your data for long-term analysis. With Prometheus, you can easily visualize and gain insights into your system's performance, allowing you to make informed decisions and optimize your services for better efficiency and reliability. -=== Need for Metrics in Microservices +== Need for Metrics in Microservices Metrics, enables developers and operators to monitor and measure the behavior of microservices at runtime. This observability is crucial for: @@ -43,7 +43,7 @@ Metrics, enables developers and operators to monitor and measure the behavior of - *Troubleshooting*: Quickly pinpointing issues by analyzing trends in performance metrics, leading to reduced downtime. - *Service Health Monitoring*: Complementing the MicroProfile Health checks by providing deeper insights into the internal state of a service, beyond simple up/down statuses. -=== Types of Metrics +== Types of Metrics MicroProfile Metrics offers a range of customizable metrics that can be used to measure and monitor microservices' performance. It allows developing microservices that are observable, manageable, and which provide insights into their behavior. @@ -61,7 +61,7 @@ Note: Counters, Histograms, and Timers, which are updated synchronously when ann By leveraging these metrics, developers and operators can gain a deeper understanding of how their microservices are performing. They can use this information to identify areas where improvements can be made and optimize their microservices' performance. -=== MicroProfile Metrics Dependency +== MicroProfile Metrics Dependency If you're using Maven, add the following dependency to your pom.xml file located in the root folder of your project: @@ -76,14 +76,14 @@ If you're using Maven, add the following dependency to your pom.xml file located For Gradle, add the corresponding dependency to your build.gradle file located within the root folder of your project: -[source] +[source, gradle] ---- dependencies { providedCompile 'org.eclipse.microprofile.metrics:microprofile-metrics-api:5.1.1' } ---- -=== Metrics Annotations +== Metrics Annotations MicroProfile Metrics defines a set of annotations to be used for exposing metrics. These annotations can be used on classes, methods, or fields. Table 7-1 shows the list of Metrics Annotation along with their descriptions. @@ -103,7 +103,7 @@ MicroProfile Metrics defines a set of annotations to be used for exposing metric Besides annotations, MicroProfile Metrics also defines a set of programmatic APIs for working with metrics. These APIs can be used to register custom metrics or access existing metrics. -=== Categories of Metrics +== Categories of Metrics In MicroProfile Metrics, metrics are organized into three distinct scopes: Base, Vendor, and Application. This categorization is designed to clearly separate metrics by their origin and relevance, making it easier for developers and operators to monitor and manage the performance of their microservices. Each scope serves a specific purpose and contains a different set of metrics: @@ -120,11 +120,11 @@ Besides the standard scopes above, MicroProfile Metrics also supports custom sco Note: In version 5.x, base metrics have become optional. This allows for flexibility in environments where these metrics may not be necessary or where they can be sourced from alternative monitoring tools. -=== Metric Registry +== Metric Registry The *MetricRegistry* component acts as a container for storing and managing metrics within an application. It provides a structured way to collect, organize, and access various types of metrics (e.g., counters, gauges, histograms, and timers) for monitoring the behavior and performance of applications. It offers a centralized repository where metrics can be created and retrieved. This allows applications to consistently monitor critical operational and performance statistics. -==== Types of Metric Registries +=== Types of Metric Registries MicroProfile Metrics creates metric registries for each scope: @@ -136,15 +136,15 @@ MicroProfile Metrics creates metric registries for each scope: A metric registry is created as per the above scopes to enable the organization of metrics based on their origin and relevance. -==== Instrumenting Microservices with MicroProfile Metrics +=== Instrumenting Microservices with MicroProfile Metrics Instrumenting microservices with MicroProfile Metrics enables developers to gain detailed insights into their application's operational health and performance. This level of observability is essential for maintaining scalable and resilient microservice architectures in dynamic environments. -==== Tracking response time using `@Timed` +=== Tracking response time using `@Timed` MicroProfile Metrics also allows you to track a method's response time as a timed metric. The code example below shows how to use the @Timed annotation to track the response time. -[source] +[source, java] ---- import org.eclipse.microprofile.metrics.annotation.Timed; // … @@ -157,7 +157,7 @@ public class ProductResource { tags = {"method=getProduct"}, absolute = true, description = "Time spent looking up products") - public Product getProduct(@PathParam("id") Long productId) { + public Response getProductById(@PathParam("id") Long id) { return productService.getProduct(productId); } @@ -165,7 +165,7 @@ public class ProductResource { ---- It will expose a metric called `productLookupTime`, which will track the amount of time spent in the `getProduct()` method in seconds. -You can visit the following URL `++https://localhost:/metrics?scope=application++` (Replace `` with the actual port where the server is running) to see the response time of this method as below: +You can visit the following URL `https://:/metrics?scope=application` (Replace `` and `` with the actual hostname and port where the server is running) to see the response time of this method as below: [source] ---- @@ -176,7 +176,7 @@ productLookupTime_seconds_max{method="getProduct",mp_scope="application",} 0.002 … ---- -==== Tracking number of invocations using `@Counted` +=== Tracking number of invocations using `@Counted` MicroProfile Metrics also allows you to track the number of invocations of a method as a counter metric. The code example below shows how to use the `@Counted` annotation to track the invocation count. @@ -190,7 +190,7 @@ public class ProductResource { @Counted(name = "productAccessCount", absolute = true, description = "Number of times the list of products is requested") - public Response getProducts() { + public Response getAllProducts() { // Method implementation // .... } @@ -198,7 +198,7 @@ public class ProductResource { ---- In the example above, the `@Counted` annotation tells MicroProfile Metrics to track the number of invocations of the `getProducts()` method and expose this metric as a counter. The name, and description of the metric can also be specified. -You can visit the following URL `++https://localhost:/metrics?scope=application++` (Replace `` with the actual port where the server is running) to see the number of times this method is called as below: +You can visit the following URL `https://localhost:/metrics?scope=application` (Replace `` with the actual port where the server is running) to see the number of times this method is called as below: [source] ---- @@ -209,7 +209,7 @@ productAccessCount_total{mp_scope="application",} 3.0 … ---- -=== Creating a Custom Metric +== Creating a Custom Metric Creating a custom metric to track the number of products in a catalog involves using the MicroProfile Metrics API. This custom metric can be implemented as a gauge, which measures an instantaneous value (in this case, the current number of products in the catalog). @@ -241,8 +241,15 @@ The gauge metric `productCatalogSize` can be accessed through the following endp This custom metric implementation provides a real-time insight into the size of your product catalog, which can be invaluable for monitoring the scale of your service's data and understanding its behavior over time. +[source] +---- +# HELP io_microprofile_tutorial_store_product_resource_ProductResource_productCatalogSize Current number of products in the catalog +# TYPE io_microprofile_tutorial_store_product_resource_ProductResource_productCatalogSize gauge +io_microprofile_tutorial_store_product_resource_ProductResource_productCatalogSize{mp_scope="application",} 8.0 +---- + Vendors may, by their own implementation, support `/metrics?name=` to directly retrieve that metric from all scopes. However, the specification itself only illustrates `/metrics?scope=&name=`. -=== Summary +== Summary This Chapter delved into the intricacies of MicroProfile Metrics, illuminating its role as a pivotal specification for efficiently monitoring microservices. Now you are equipped with a thorough understanding of diverse metric types and their application for monitoring microservice performance. This chapter highlighted the need for regular microservice monitoring via metrics and health checks, emphasizing metrics for detailed performance insights such as response times and throughput. Through practical examples, this chapter showcases how to instrument microservices with MicroProfile Metrics, leveraging standard metrics, and creating custom metrics to monitor microservices comprehensively. diff --git a/chatper08/chapter08.adoc b/modules/ROOT/pages/chapter08/chapter08.adoc similarity index 97% rename from chatper08/chapter08.adoc rename to modules/ROOT/pages/chapter08/chapter08.adoc index ea56f17b..ea5ed540 100644 --- a/chatper08/chapter08.adoc +++ b/modules/ROOT/pages/chapter08/chapter08.adoc @@ -1,4 +1,4 @@ -= Chapter 8: MicroProfile Fault Tolerance += MicroProfile Fault Tolerance In a Microservices architecture, an application consists of multiple smaller, autonomous services. This architecture enhances development flexibility, agility, and scalability but introduces new challenges, particularly in ensuring the application's reliability and managing failures. Unlike monolithic applications, where defects are localized, a single failure in one microservice can propagate across the entire application, potentially causing widespread outages. Therefore, fault tolerance is critical in a microservices architecture to ensure that failures are seamlessly isolated, managed, and recovered. @@ -55,7 +55,10 @@ A fallback provides a default response if an operation fails. It ensures the sys A circuit breaker stops an application from making too many unsuccessful requests to another system. If the number of failures exceeds a threshold, the circuit breaker will `open`, causing all subsequent requests to fail immediately. After a configured delay, the circuit breaker will `half-open` and allow limited requests. If those requests succeed, the circuit breaker will `close` and let all requests go through. -image::../images/figure8-1.png[Figure 8-1: Circuit Breaker states, width=600, align="center"] +:imagesdir: ../../assets/images +:figure-caption: MicroProfile Fault Tolerance +.MicroProfile Fault Tolerance +image::figure8-1.png[MicroProfile Fault Tolerance] For example, a circuit breaker can be applied to calls to an external inventory service in the Product Catalog Microservice. If the inventory service starts failing or becomes unresponsive, the circuit breaker will `open`, preventing repeated requests and reducing load. After a configured delay, the circuit breaker will `half-open` to test the availability of the inventory service with a few requests. If those succeed, the circuit breaker will `close`, resuming normal operations. @@ -137,7 +140,7 @@ To store the necessary payment information, the following `PaymentDetails` class [source,java] ---- -class PaymentDetails { +public class PaymentDetails { private double amount; public double getAmount() { @@ -169,7 +172,7 @@ The `CriticalPaymentException` is considered a non-recoverable failure. If this ---- package io.microprofile.tutorial.store.payment.exception; -class CriticalPaymentException extends Exception { +public class CriticalPaymentException extends Exception { public CriticalPaymentException(String message) { super(message); } @@ -199,7 +202,7 @@ A retry policy specifies the conditions under which an operation should be retri Retry policies can be externalized using the MicroProfile Config API. This allows you to modify the retry behavior without changing the application code. Here’s how to externalize the configuration: -1. Add the `@Retry` annotation with minimal attributes: +* Add the `@Retry` annotation with minimal attributes: [source, java] ---- @@ -225,7 +228,7 @@ public class PaymentService { } ---- -2. Define the retry policy in a configuration file (e.g., microprofile-config.properties): +* Define the retry policy in a configuration file (e.g., microprofile-config.properties): [source] ---- @@ -292,7 +295,7 @@ public String getProduct(Long id) { if (Math.random() > 0.7) { throw new RuntimeException("Simulated service failure"); } - return productRepository.findProductById(id); + return repository.findProductById(id); } ---- @@ -302,7 +305,7 @@ In the above code, the circuit breaker opens if 50% of requests fail (`failureRa Using MicroProfile Config, you can externalize circuit breaker parameters to make them adjustable without code changes as below: -1. Update the `@CircuitBreaker` annotation: +* Update the `@CircuitBreaker` annotation: [source, java] ---- @@ -316,7 +319,7 @@ public String getProduct(Long id) { } ---- -2. Define the configuration in *microprofile-config.properties*: +* Define the configuration in *microprofile-config.properties*: ---- io.microprofile.tutorial.store.payment.service.ProductService/fetchProductDetails/CircuitBreaker/requestVolumeThreshold=10 @@ -388,7 +391,7 @@ public class PaymentService { ==== Externalizing Timeout Configuration Timeout values can be externalized using the MicroProfile Config API, allowing flexibility to adjust values without modifying code. Here’s how: -1. Define the @Timeout annotation without specifying the value: +* Define the @Timeout annotation without specifying the value: [source, java] ---- @@ -398,7 +401,7 @@ public String fetchData() { } ---- -2. Configure the timeout in *microprofile-config.properties*: +* Configure the timeout in *microprofile-config.properties*: [source] ---- @@ -637,7 +640,7 @@ This example demonstrates the use of MicroProfile Fault Tolerance annotations `@ To externalize the @Timeout configuration using MicroProfile Config, you can replace the hardcoded timeout value with a configurable property. This allows us to modify the timeout dynamically without changing the source code. -1. Define a Configurable Property: Use `@ConfigProperty` to inject the timeout value. +* Define a Configurable Property: Use `@ConfigProperty` to inject the timeout value. [source, java] ---- @@ -660,7 +663,7 @@ public class ProductService { // ... ---- -2. Use the Configured Value in @Timeout Annotation: Define a getter method and using it in the annotation. +* Use the Configured Value in @Timeout Annotation: Define a getter method and using it in the annotation. [source, java] ---- @@ -674,7 +677,7 @@ public class ProductService { } ---- -3. Define the Configuration Property: Configure the timeout in *microprofile-config.properties*: +* Define the Configuration Property: Configure the timeout in *microprofile-config.properties*: [source] ---- diff --git a/chapter09/index.adoc b/modules/ROOT/pages/chapter09/index.adoc similarity index 99% rename from chapter09/index.adoc rename to modules/ROOT/pages/chapter09/index.adoc index 3e768fe9..912de480 100644 --- a/chapter09/index.adoc +++ b/modules/ROOT/pages/chapter09/index.adoc @@ -1,4 +1,4 @@ -= Chapter 9: MicroProfile Telemetry += MicroProfile Telemetry Microservices-based applications have better scalability, flexibility, and resilience, but they suffer from additional challenges regarding availability and performance monitoring. This makes observability critical to ensure these distributed systems operate reliably. diff --git a/modules/ROOT/pages/chapter10/chapter10.adoc b/modules/ROOT/pages/chapter10/chapter10.adoc new file mode 100644 index 00000000..71227b75 --- /dev/null +++ b/modules/ROOT/pages/chapter10/chapter10.adoc @@ -0,0 +1,488 @@ += JWT Authentication +:id: chapter10 + +In modern microservices architectures, where services are distributed and stateless, +securing communications between clients and services and between individual services is critical. *JSON Web Token (JWT)* provides a lightweight, self-contained, and efficient user authentication and authorization mechanism, enabling scalable and secure identity propagation across distributed systems. + +*MicroProfile JWT* is a specification that standardizes JWT-based authentication and authorization for Java microservices. Leveraging the JWT open standard https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] enables services to securely extract and validate claims such as identity, roles, and permissions. + +MicroProfile JWT allows developers to build secure, interoperable, and portable microservices. It supports *role-based access control (RBAC)*, simplifies identity management in stateless services, and avoids vendor lock-in by adhering to open specifications. + +== Topics to be covered: +- Introduction to JWT Authentication +- Structure of JWT +- Use cases for JSON Web Tokens +- Benefits of JWT in Microservices +- Setting up MicroProfile JWT +- Configuring MicroProfile JWT Validation +- Request Flow in MicroProfile JWT +- Role-Based Access Control (RBAC) +- Setting Token Expiry Times for Security +- Error Handling +- Best Practices for JWT Authentication +- Security Best Practices for Microservices +- Conclusion + +== Introduction to JWT Authentication + +This section will explore JSON Web Tokens, how they work, and why they are foundational to implementing stateless authentication and authorization in microservices-based systems. + +=== What is a JSON Web Token (JWT)? + +A **JSON Web Token (JWT)** (see https://jwt.io/[JWT.io]), as defined in https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519], is an open standard for securely transmitting information (claims) between parties as a JSON object. JWTs are digitally signed, ensuring their integrity and authenticity. + +=== Structure of a JWT + +A JWT consists of three Base64 encoded parts, separated by dots (+.+): + +[source] +---- +
.. +---- + +- *Header* - It contains metadata about the token, such as token type (type: “JWT”) and signing algorithm ( alg: “RS256” for RSA-SHA256). + +[source, json] +---- +{ + "alg": "RS256", + "typ": "JWT" +} +---- +- *Payload* - It contains claims which are key-value pairs representing data about the user, such as roles and expiration. +Example of claims in a JWT payload: + +[source, json] +---- +{ + "iss": "https://io.microprofile.com/issuer", + "sub": "user1", + "exp": 1735689600, + "iat": 1735686000, + "aud": "my-audience", + "groups": ["user", "admin"] +} +---- + +- *Signature* — A digital signature that verifies the token’s integrity by combining the encoded header, payload, and private key. + +Example JWT Token: +[source] +---- +eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. +QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM +oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG +TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima +sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52 +YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a +1rZgN5TiysnmzTROF869lQ. +AxY8DCtDaGlsbGljb3RoZQ. +MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM +HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8. +fiK51VwhsxJ-siBMR-YFiA +---- + +=== Types of Claims in MicroProfile JWT + +Claims in JWTs can be categorized into two types: + +==== Standard Claims + +These are predefined claims with specific meanings, as defined by the JWT specification. Some commonly used standard claims include: + +|=== +|*Claim*|*Description*|*Example* + +|`iss`|Issuer, the entity that issued the JWT (e.g., an authentication server)|`"iss": "https://io.microprofile.com/issuer"` +|`sub`|Subject, the principal (user or service) that the JWT is about.|`"sub": "user1"` +|`aud`|Audience, the intended recipients of the token (e.g., specific microservices).|`"aud": "order-service"` +|`exp`|Expiration time|`"exp": 1735689600` +|`nbf`|not before|`"nbf": 1735686000` +|`iat`|issued at, when that token was issued.|`"iat": 1735686000` +|`jti`|Unique JWT token identifier|`"jti": "a1b2c3d4"` +|`groups`|Groups, list of roles or users allowed to access the resource |`["user", "admin"]` +|=== + +==== Custom Claims + +These are application-specific claims that provide additional information about the user or entity. They can extend authorization logic with application-specific claims (e.g., `department`, `region`). Custom claims are not part of the JWT specification but often include domain-specific data, such as user preferences, tenant IDs, or other metadata. MicroProfile JWT allows developers to access these claims programmatically. + +== Use cases for JSON Web Tokens + +JWTs are versatile tokens commonly used in modern applications for authentication, where they verify the identity of a user or service; for authorization, where they grant access to resources based on roles or permissions; and for information exchange, where they securely transmit data between parties. + +Below are key scenarios where JWTs shine in microservices environments: + +=== Authentication + +JWTs enable stateless authentication in distributed systems. When a user logs in, an authentication service issues a JWT containing claims like sub (user ID) and exp (expiration time). The client sends this token in the `Authorization: Bearer` header of subsequent requests, allowing microservices to verify the user’s identity without requiring repeated authentication. + +For example, a user authenticates with an Auth Service and receives a JWT. This JWT token grants access to other services, such as a product catalog or order management system, without re-authentication. + +=== Authorization (Role-Based Access Control) + +JWTs are also used for authorization, enabling fine-grained access control based on user roles or permissions. The JWT payload typically includes a group or role claim specifying the user’s roles or permissions. For example, a user with the admin role might be allowed to access all resources while a user with the user role might only have access to specific resources. + +MicroProfile JWT integrates seamlessly with Jakarta EE’s `@RolesAllowed` annotation, making it easy to enforce role-based access control (RBAC) in microservices. Role mapping can be configured in _microprofile-config.properties_: + +[source] +---- +mp.jwt.verify.roles=groups +---- + +=== Claims-based identity + +JWTs are often used to represent claims-based identity, where the JWT contains claims representing the user’s identity, such as their name, email address, or other attributes. Applications can use these claims to identify the user and personalize their experience. + +For example, an application might use the email claim to look up the user’s profile information in a database or +display the user’s name on a welcome page using the name claim. + +=== Information Exchange + +JWTs can securely exchange information between parties. The token payload can include custom claims representing the data being exchanged, such as an order ID or user ID. This makes JWTs useful in scenarios like Single Sign-On (SSO) systems, where information needs to be shared across multiple services. + +For example, a JWT might contain an `order_id` claim and a `user_id` claim, which an order management service can use to retrieve and display the user’s order details. + +=== Federation & Single Sign-On (SSO) + +JWTs facilitate identity federation by allowing integration of multiple trusted identity providers (e.g., Active Directory, LDAP) to provide a single sign-on (SSO) experience. In this case, the JWT contains claims representing the user’s identity, which applications can use to identify the user and retrieve their profile information. + +For example, an enterprise SSO system can issue a JWT that grants access to HR, Payroll, and CRM microservices. MicroProfile JWT validates the token’s iss (issuer) and aud (audience) to enforce trust boundaries. + +== Benefits of using JWT in Microservices + +JWTs are widely used in microservices for the following reasons: + +=== Statelessness & Scalability + +JWTs eliminate the need for centralized session storage. Each token is self-contained, embedding all necessary user claims (e.g., roles, permissions) in its payload. + +Independent Validation: Microservices validate JWTs locally using public keys, avoiding calls to a central authority. This reduces latency and scales horizontally. + +Example: +A payment service validates a JWT’s signature without querying an authentication server. + + +=== Interoperability + +Open Standards: JWTs adhere to RFC 7519, ensuring compatibility across platforms (Java, .NET, Node.js) and frameworks (Spring Boot, Quarkus). + +MicroProfile Integration: MicroProfile JWT standardizes validation and claim extraction, enabling seamless interoperability across Java microservices. + +=== Fine-Grained Authorization + +Role-Based Access Control (RBAC): Map JWT claims (e.g., groups) to Jakarta EE roles using @RolesAllowed. + +=== Decentralized Security + +Propagation Across Services: A JWT issued by an authentication service is propagated across microservices (e.g., the Order Service and the Inventory Service). Each service independently verifies the token and enforces access control. + +Reduced Central Dependency: No need for a central authorization server, simplifying architecture and improving +fault tolerance. + +Example: + +- Authentication Service: Issues a JWT with `sub: "user1"` and `groups: ["user"]`. +- Order Service: Validates the JWT and processes requests if groups include `users`. +- Inventory Service: Revalidates the same JWT without contacting the auth service. + +== Setting Up MicroProfile JWT + +To use MicroProfile JWT in your project, add the following dependency to your _pom.xml_ (for Maven): + +[source, xml] +---- + + org.eclipse.microprofile.jwt + microprofile-jwt-auth-api + 2.1 + provided + +---- + +For Gradle, add the following to your _build.gradle_: + +[source] +---- +implementation 'org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1' +---- + +== Configuring MicroProfile JWT Validation + +MicroProfile JWT requires validation rules configuration to be defined in `src/main/resources/microprofile-config.properties` file. Below is an example configuration: + +[source] +---- +# Public key (PEM format) to verify JWT signatures +mp.jwt.verify.publickey.location=META-INF/publicKey.pem + +# Expected issuer (e.g., your OIDC provider) +mp.jwt.verify.issuer=https://auth.example.com + +# Optional: Validate token audience +mp.jwt.verify.audiences=order-service,payment-service +---- + +Explanation: + +- The `mp.jwt.verify.publickey.location` property specifies the location of the public key used to verify the JWT’s signature. + +- The `mp.jwt.verify.issuer` property defines the expected issuer of the JWT, ensuring that tokens are only accepted if issued by a trusted authority. + +- Optionally, the `mp.jwt.verify.audiences` property can specify the allowed audiences for the JWT, ensuring that the token is intended for the service. + +=== Public Key Setup + +Place the PEM-encoded public key in _src/main/resources/META-INF/publicKey.pem_. This key is used to verify incoming JWT signatures. + +== Request Flow in MicroProfile JWT + +Understanding how JWTs are propagated and processed in a microservices architecture is critical to implementing secure and scalable authentication. This section explains the lifecycle of a JWT from client to service, including token extraction, validation, and claim usage. + +=== How JWTs are Propagated in Microservices + +JWTs are propagated via the `Authorization: Bearer` HTTP header across clients and services. + +==== Client-to-Service + +When a client authenticates (e.g., via a login endpoint), it receives a JWT from an authentication service. This token is then included in the header of subsequent requests to microservices. For example, a request header might look like this: + +[source] +---- +GET /api/orders HTTP/1.1 +Authorization: Bearer eyJhbGciOiJSUzI1NiIs… +---- + +==== Service-to-Service + +In microservices architecture, a client sends a JWT token to the initial service (for example, Order Service) using the `Authorization: Bearer` header. The initial service can forward the same token when calling another backend service (for example, the Inventory Service). Each microservice independently validates the JWT to enforce decentralized, stateless security. + +In advanced scenarios involving multiple downstream services, careful consideration must be given to validating the JWT's `aud` (audience) claim to ensure the token is intended for the target service. + +==== Token Extraction + +MicroProfile JWT runtime handles token extraction and validation automatically. The token is parsed and validated as follows: + +- Header Parsing: The runtime extracts the token from the Bearer schema. + +- Decoding: The JWT is split into its header, payload, and signature components. + +==== Token Validation +The token validation involves the following steps: + +- Signature Verification: The public key validates the token’s integrity. + +- Standard Claims Validation: The runtime then validates standard claims: + +. `iss`: It should match the `mp.jwt.verify.issuer` configuration property. + +. `exp` : This checks if the token has not expired. + +. `aud` : Optionally it checks for the included service(s) in `mp.jwt.verify.audiences`. + +If valid, the JWT’s claims populate the `SecurityContext`. Otherwise, MicroProfile JWT rejects the request with a `401 Unauthorized` status. + +=== Accessing JWT claims via `SecurityContext` + +The `SecurityContext` interface (from Jakarta EE) provides programmatic access to JWT claims. Once a token is validated, MicroProfile JWT injects the `JsonWebToken` into the `SecurityContext`, allowing developers to: + +- Retrieve user identity (e.g., `sub` claim). + +- Check user roles (e.g., `groups` claim). + +- Access custom claims (e.g., `tenant_id` claim). + +[source, java] +---- +@GET +@Path("/user-profile") +public String getUserProfile(@Context SecurityContext ctx) { + JsonWebToken jwt = (JsonWebToken) ctx.getUserPrincipal(); + String userId = jwt.getName(); // Extracts the "sub" claim + Set roles = jwt.getGroups(); // Extracts the "groups" claim + String tenant = jwt.getClaim("tenant_id"); // Custom claim + + return "User: " + userId + ", Roles: " + roles + ", Tenant: " + tenant; +} +---- + +The `SecurityContext` simplifies working with JWTs, enabling seamless integration with Jakarta EE’s security annotations like `@RolesAllowed`. By calling `securityContext.getUserPrincipal()`, the application can obtain the `JsonWebToken` instance, which contains all the claims from the JWT. + +== Role-Based Access Control (RBAC) + +MicroProfile JWT simplifies RBAC by mapping JWT claims (e.g., `groups` or `roles`) to Jakarta EE roles. This enables declarative security using the `@RolesAllowed` annotation. This section explains how to configure and use this mapping effectively. + +=== Default Role Mapping with the `groups` Claim + +MicroProfile JWT seamlessly integrates with Jakarta EE’s `@RolesAllowed` annotation to enforce role-based access control in microservices. By default, MicroProfile JWT maps roles from the groups claim in the JWT payload to Jakarta EE roles. The groups claim is a standard JWT claim that represents the roles or groups assigned to the user. For example, a JWT payload might include: + +[source] +---- +{ + "iss": "https://example.com/issuer", + "sub": "user123", + "groups": ["user", "admin"] +} +---- + +In this case, the user has two roles: user and admin. + +=== Securing Endpoints +The roles in the groups claim can be used directly with the `@RolesAllowed` annotation to secure endpoints. + +[source, java] +---- +@Path("/orders") +public class OrderResource { + + @GET + @Path("/{id}") + @RolesAllowed("user") // Only users can access this method + public Response getOrder(@PathParam("id") String id, @Context SecurityContext ctx) { + String user = ctx.getUserPrincipal().getName(); + // Fetch order for the user + return Response.ok("Order for user: " + user + ", ID: " + id).build(); + } + + @DELETE + @Path("/{id}") + @RolesAllowed("admin") // Only admins can access this method + public Response deleteOrder(@PathParam("id") String id, @Context SecurityContext ctx) { + String admin = ctx.getUserPrincipal().getName(); + // Delete order as admin + return Response.ok("Order deleted by admin: " + admin + ", ID: " + id).build(); + } +} +---- + +The `GET /orders/{id}` service is accessible to users, whereas the `DELETE /orders/{id}` is only available to users with the admin role. + +=== Custom Role Mapping + +If your JWT uses a claim other than groups to represent roles (e.g., roles or scopes), you can customize the mapping using the `mp.jwt.verify.roles` property in _microprofile-config.properties_: + +[source] +---- +# Optional: Map roles from the "groups" claim (default behavior) +mp.jwt.verify.roles=groups +---- + +The `groups` claim is the default claim used for role mapping in MicroProfile JWT Authentication. Therefore, you typically do not need to set the `mp.jwt.verify.roles` property unless your JWT uses a different claim name. For example, some identity providers (like OAuth 2.0 servers or OpenID Connect providers) might include roles in claims such as `roles`, `permissions`, or `scope` instead of `groups`. + +In such cases, update the mapping in your _microprofile-config.properties_ file: + +[source] +---- +mp.jwt.verify.roles=roles +---- + +This ensures that MicroProfile JWT Authentication correctly maps the roles for use with Jakarta EE security annotations like `@RolesAllowed`. + +==== How the RBAC Works + +- Token Validation: MicroProfile JWT validates the JWT’s signature and claims. + +- Role Extraction: Roles are extracted from the configured claim (groups by default). + +- Access Control: The `@RolesAllowed` annotation checks if the user’s roles match the required roles. If not, a `403 Forbidden` response is returned. + +This approach ensures fine-grained security while maintaining compatibility with standard JWT practices. + +== Setting Token Expiry Times for Security + +Short token expiry times reduce the surface area for the attackers. Here’s how to configure token expiry effectively: + +=== Configuring Token Expiry + +Set the `exp` claim at issuance: Ensure your authentication service issues tokens with the `exp` claim. + +[source, java] +---- +{ + "exp": 1735689600 // Token expires at 2025-01-01 00:00:00 UTC +} +---- + +MicroProfile JWT automatically validates the `exp` claim during token verification. Beyond standard JWT validation settings, no additional configuration is needed. + +MicroProfile JWT will reject tokens returning a 401 Unauthorized response if: + +- The `exp` claim is missing or invalid. + +- The current time exceeds the `exp` value. + +== Error Handling + +MicroProfile JWT automatically validates tokens and rejects invalid requests with standardized HTTP responses. Common scenarios include: + +=== Invalid Token (e.g., malformed JWT, invalid signature): + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="invalid_token" +---- + +=== Expired Token (exp claim validation failure): + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="invalid_token", error_description="Token expired" +---- + +=== Missing Token + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="missing_token" +---- + +=== Insufficient Permissions (e.g., missing role for @RolesAllowed): + +[source] +---- +HTTP/1.1 403 Forbidden +---- + +=== Best Practices for JWT Authentication + +. Use Standard Claims - Prefer the groups claim for roles unless your identity provider uses a different claim. + +. Consistent Role Names - Ensure role names (e.g., admin, user) are consistent across JWTs and @RolesAllowed annotations. + +. Least Privilege - Assign minimal required roles to endpoints to reduce security risks. + +. Combine with Other Annotations - Use @PermitAll or @DenyAll alongside @RolesAllowed for flexible security policies. + +== Security Best Practices for Microservices + +But with more services comes more complexity, and with more complexity comes a greater risk of security breaches. So, how do you secure your microservices? + +Securing microservices requires a layered approach, combining authentication, authorization, encryption, and monitoring. MicroProfile JWT simplifies access control while adhering to industry standards. Below are best practices tailored for MicroProfile JWT implementations: + +. Enforce Authentication with Validated JWTs: Ensure every request to a microservice includes a valid JWT. Configure MicroProfile JWT to validate tokens using a public key. Reject tokens with invalid signatures, missing claims, or expired exp values. + +. Implement Role-Based Access Control: Restrict endpoint access based on user roles defined in the JWT. Configure role mapping in `microprofile-config.properties` if using non-default claims + +Use Short-Lived Tokens: To minimize exposure to compromised tokens, set short expiration times (exp claim) for JWTs (e.g., 15–30 minutes). + +. Secure Token Transmission: Prevent token interception or tampering by using HTTPS to encrypt data in transit and store tokens in HTTP `Authorization: Bearer` headers (never in URLs or cookies). + +. Manage Cryptographic Keys Securely: Protect keys to sign/verify JWTs by storing public keys in secure locations (e.g., Kubernetes Secrets, AWS KMS). Rotate keys periodically and avoid hardcoding them in source control. + +. Validate and Sanitize JWT Claims: Validate all claims (e.g., iss, aud) in microprofile-config.properties, and Sanitize custom claims before use to prevent injection attacks and misuse of claims. + +. Monitor and Log Security Events: Log JWT validation errors, role mismatches, and token expiration events to detect breaches and audit access patterns. Integrate with monitoring tools (e.g., Prometheus, Grafana) to track anomalies. + +These steps will help you secure your microservices against the most common attacks. + +== Conclusion + +MicroProfile JWT offers a standards-based, interoperable approach for securing microservices. It simplifies identity propagation, access control, and stateless security across distributed services. Integrating with Jakarta EE enables secure, scalable, and interoperable authentication without a session state. + +*Further Reading:* + +* https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] +* https://github.com/eclipse/microprofile-jwt-auth[MicroProfile JWT 2.1 Spec] +* https://jakarta.ee/specifications/security/3.0/[Jakarta Security 3.0] diff --git a/modules/ROOT/pages/chapter11/chapter11.adoc b/modules/ROOT/pages/chapter11/chapter11.adoc new file mode 100644 index 00000000..b22a2081 --- /dev/null +++ b/modules/ROOT/pages/chapter11/chapter11.adoc @@ -0,0 +1,472 @@ += MicroProfile Rest Client +:rest-client-spec-name: MicroProfile Rest Client +:rest-client-spec-version: 3.1 +:idvar: id + +In microservices architecture, developers often face the cumbersome task of implementing boilerplate code to consume REST APIs - manually constructing HTTP requests, parsing responses, and handling errors. The MicroProfile Rest Client specification addresses this by leveraging Jakarta RESTful Web Services (formerly JAX-RS) annotations to create type-safe Rest client interfaces. Instead of writing low-level HTTP logic, developers define Java interfaces that mirror the target service’s endpoints. At runtime, MicroProfile Rest Client dynamically generates an implementation of these interfaces, automating HTTP communication while ensuring compile-time consistency between the client and server contracts. + +This chapter introduces the MicroProfile Rest Client, a type-safe framework for simplifying service-to-service communication. We will begin by defining REST client interfaces using Jakarta RESTful Web Services annotations (`@GET`, `@Path`), configuring endpoints via MicroProfile Config, and implementing HTTP invocation. Next, we will explore handling HTTP communication, processing responses, and error handling. By the end of this chapter, you will be able to replace hand-written HTTP boilerplate code with declarative, maintainable clients while adhering to Jakarta EE and MicroProfile standards. + +== Topics to be covered: + +* Introduction to MicroProfile Rest Client +* Setting up Dependencies +* Defining a Rest Client Interface +* Parameter Configuration +* Requests and Response Handling +* Working with JSON Data formats +* Error Handling Strategies + +== Introduction to MicroProfile Rest Client + +The MicroProfile Rest Client specification simplifies RESTful service consumption in Java microservices by replacing error-prone manual HTTP handling with a type-safe, annotation-driven approach. Instead of writing boilerplate code, developers define Java interfaces that mirror the target service’s API. Using Jakarta RESTful Web Services annotations like `@GET`, and `@Path`, these interfaces declaratively map methods to HTTP operations (e.g., `/users/\{id}` to `getUser(id)`). The framework then generates an implementation at runtime, automating communication while ensuring compile-time consistency between client and server contracts. Tight integration with MicroProfile Config and CDI allows seamless configuration and injection, making it ideal for building resilient, maintainable clients that align with modern microservices practices. + +== Key Features of MicroProfile Rest Client + +The MicroProfile Rest Client simplifies consuming RESTful services in Java microservices with the following key features: + +. *Type-Safe and Declarative APIs* - The MicroProfile Rest Client allows developers to define REST clients as Java interfaces using Jakarta RESTful Web Services annotations like `@GET`, `@POST`, `@PUT`, `@DELETE`, `@Path`, `@Consumes` and `@Produces`. This approach improves code clarity and ensures compile-time validation, reducing the possibility of runtime errors . +. *Integration with CDI (Context and Dependency Injection)* - This specification allows developers to seamlessly inject MicroProfile Rest Client interfaces using `@Inject` and `@RestClient` into CDI-managed beans, promoting better dependency management and integration with other components. By leveraging CDI lifecycle management, the MicroProfile Rest Client can benefit from scope management (e.g., `@ApplicationScoped`), proxying, and automatic initialization. +. *Runtime Configurable with MicroProfile Config* - The behavior of MicroProfile Rest Client can be dynamically configured using MicroProfile Config. This allows properties like the base URL and other client settings to be adjusted without recompilation. The configuration can be provided through _microprofile-config.properties_ or environment variables, making the client highly adaptable to different environments. +. *Support for Asynchronous Execution* - For asynchronous execution, MicroProfile Rest Client can return `CompletionStage`, allowing non-blocking requests. This significantly improves performance & scalability in high-concurrency environments. +. *Automatic Handling of Redirect Responses* - MicroProfile Rest Client can automatically follow HTTP redirects, simplifying client implementation when working with services that return `3xx` responses. +. *Secure Socket Layer (SSL) and Security Configuration* - Supports SSL/TLS configuration, including certificates and trust stores, ensuring secure communication between microservices. +. *Propagation of Headers and Cookies* - Enables automatic propagation of HTTP headers, cookies and context (e.g., authentication tokens), facilitating session management across service calls. +. *Exception Handling and Custom Providers* - Allows custom exception mapping and response handling, giving developers control over error response based on specific conditions, improving fault tolerance and user experience. +. *Integration with MicroProfile Fault Tolerance* - This specification Supports resilience patterns like retries (`@Retry`), circuit breakers (`@CircuitBreaker`), and Bulkheads (`@Bulkhead`), ensuring stability in service-to-service communications. +. *Integration with MicroProfile Long Running Actions (LRA)* - MicroProfile Rest Client can coordinate distributed transactions using LRA annotations (e.g., `@LRA`), enabling compensation logic for long-running processes. This ensures consistency across services in complex workflows. +. *Portability and Standards Compliance*: This specification enables MicroProfile Rest Client to work across different MicroProfile-compatible runtimes, leveraging Jakarta EE standards (CDI, Jakarta RESTful Web Services, JSON Binding, JSON Processing). + +== Setting up Dependency for MicroProfile Rest Client + +To use MicroProfile Rest Client 3.1 in your project, you need to include the necessary dependencies in your build configuration. Below are configurations for Maven and Gradle: + +=== Maven Configuration +For Maven-based projects, add the following dependency to your pom.xml file: + +[source, xml] +---- + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + 3.1 + +---- + +=== Gradle Configuration + +For Gradle-based projects, add the following dependency to your build.gradle file: + +[source, xml] +---- +dependencies { + Implementation 'org.eclipse.microprofile.rest.client:microprofile-rest-client-api:3.1' + compileOnly 'org.eclipse.microprofile:microprofile:6.1' +} +---- + +> Tip: The MicroProfile Rest Client is an Eclipse Foundation project. For more details and updates on the project, visit the official repository: MicroProfile Rest Client on GitHub. + +== Creating MicroProfile Rest Client Interface + +To create a MicroProfile Rest Client interface, you need to define a Java interface and annotate it with annotations to map it to a RESTful service. + +=== The `@RegisterRestClient` Annotation + +To use the MicroProfile Rest Client, annotate your client interface with `@RegisterRestClient`. This annotation registers the interface as a Rest client within MicroProfile runtime and enables it as a CDI bean, allowing it to be injected into other components. + +Example: + +[source, java] +---- +package io.microprofile.tutorial.inventory.client; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.microprofile.tutorial.inventory.dto.Product; + +@RegisterRestClient(configKey = "product-service") +@Path("/products") +public interface ProductServiceClient { + + @GET + @Path("/{id}") + Product getProductById(@PathParam("id") Long id); +} +---- + +Explanation: +In the above code, we define a `ProductServiceClient` within the package `io.microprofile.tutorial.inventory.client`. The interface serves as a Rest client for interaction with a remote product service. + +. `@RegisterRestClient` - declares the `ProductServiceClient` interface as a MicroProfile Rest Client, enabling it to be injected into other CDI-managed components. + +. `configKey = "product-service"` - associates the client with a configuration key, allowing dynamic configuration via MicroProfile Config (e.g., using _microprofile-config.properties_ or environment variables). + +. `@Path(/products)` - specifies the base URI path segement for the RESTful service. + +. `@GET` - indicates that the `getProductById()` method handles HTTP GET requests. + +. `@Path("/\{id}")` – define a dynamic URI path parameter `\{id}`, which will be replaced at runtime with the actual value provided. + +. `@PathParam("id")` - binds the method parameter `id` to the `\{id}` placeholder in the request URL. + +. Return Type (`Product`) - specifies that the method returns a `Product` Data Transfer Object (DTO), representing the retrieved product data. + +> Note: In CDI environments, it is recommended not to extend AutoCloseable in REST client interfaces. The container manages the lifecycle of injected clients automatically, ensuring proper resource handling without requiring manual closure. + +==== Configuration via MicroProfile Config: + +To configure the URI using MicroProfile Config, you need to add a config file named src/main/webapp/META-INF/microprofile-config.properties in your project. This file contains the configuration key and value pairs. In this example, we’re configuring the base URI to http://localhost:8080/api/products. We can configure other client properties, such as followRedirects. The followRedirects property specifies whether the client should automatically follow HTTP redirects (3xx status codes) when making RESTful web service calls. + +[source] +---- +product-service/mp-rest/url=http://localhost:8080/api/products +product-service/mp-rest/followRedirects=true +---- + +== Parameter Configurations + +In MicroProfile Rest Client, you can dynamically configure headers, query parameters, and path parameters using Jakarta RESTful Web Services annotations. These annotations bind method parameters to different parts of the HTTP request, enabling flexible and dynamic RESTful client interfaces that can efficiently interact with various endpoints. + +*Supported Parameter Annotations* + +. `@PathParam` – Binds a method parameter to a path variable in the URL. + +. `@QueryParam` – Maps a method parameter to a query string parameter in the request URL. + +. `@HeaderParam` – Attaches a method parameter to an HTTP request header. + +=== Using Path Parameters (`@PathParam`) + +Path parameters are used to insert dynamic values directly into the URL path. + +[source, java] +---- +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/products") +public interface ProductServiceClient { + + @GET + @Path("/{id}") + Product getProductById(@PathParam("id") Long id); +} +---- + +Example +[source, java] +---- +productServiceClient.getProductById(1L); +---- + +Resulting HTTP Request +[source, http] +---- +GET /products/1 +---- + +==== Why Use @PathParam? + +. Ensures URL structure consistency by enforcing path variables +. Prevents hardcoding URLs, making the code cleaner and maintainable. + +=== Using Query Parameters (`@QueryParam`) + +Query parameters are typically used for filtering, pagination, or optional parameters in the request URL. + +Example: + +[source, java] +---- +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/products") +public interface ProductServiceClient { + + @GET + List getProductsByCategory(@QueryParam("category") String category); +} +---- + +Example Call: + +[source, java] +---- +productServiceClient.getProductsByCategory("electronics"); +---- + +Resulting HTTP Request: +[source, http] +---- +GET /products?category=electronics +---- + +==== Why Use @QueryParam? +. Useful for filtering results (?category=electronics). +. Ideal for pagination (?page=2&size=20). +. Allows sending optional parameters without modifying the URL structure. + +=== Using Header Parameters (@HeaderParam) + +Header parameters are typically used for authentication, authorization, and metadata transmission between client and server. + +Example: + +[source, java] +---- +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.Path; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/orders") +public interface OrderServiceClient { + + @GET + List getOrders(@HeaderParam("Authorization") String authToken); +} +---- + +Example Call: + +[source, java] +---- +orderServiceClient.getOrders("Bearer my-secret-token"); +---- + +Resulting HTTP Request: + +[source] +---- +GET /orders +Authorization: Bearer my-secret-token +---- + +==== Why Use @HeaderParam? +. Used for passing authentication tokens (Authorization: Bearer token). +. Helps with custom metadata exchange (e.g., X-Correlation-ID: 12345). +. Avoids exposing sensitive data in URLs (e.g., API keys). + +=== Overview of Additional Annotations + +. `@CookieParam` - Binds a method parameter to the value of an HTTP cookie in the incoming request. + +. `@FormParam` — Maps a method parameter to a field in a submitted HTML form (`application/x-www-form-urlencoded` POST body). + +. `@MatrixParam` — Binds a method parameter to a matrix parameter embedded within the URL path segements (e.g., `/product;color=blue;size=large`). + +. `@BeanParam` — Aggregates multiple parameter annotations (path, query, header, etc.) into a single Java bean for cleaner method signature. + +> Tip: These annotations eliminate manual string concatenation, making REST client calls type-safe and maintainable. + +== Handling Requests and Responses + +In MicroProfile Rest Client, handling requests and responses involves defining methods in your interface that map to RESTful service endpoints. This ensures that: + +. HTTP requests are automatically constructed based on method definitions. +. Responses are efficiently deserialized into Java objects (DTOs) or processed manually using `Response`. + +Using Jakarta RESTful Web Services annotations, you can define standard HTTP operations such as @GET, @POST, @PUT, and @DELETE. The framework also supports additional methods like @HEAD, @OPTIONS, and @PATCH, providing complete control over HTTP communication when needed. Meanwhile, MicroProfile automatically handles serialization, deserialization, and request execution at runtime. + +== Handling JSON Data formats + +By default, MicroProfile Rest Client supports JSON format without requiring additional configurations. Serialization and deserialization of request and response bodies are automatically handled using JSON-B (Jakarta JSON Binding) or JSON-P (Jakarta JSON Processing). + +Developers can directly use Java objects as request bodies or response entities, eliminating the need for manual parsing. + +Example: + +[source, java] +---- +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.Consumes; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient +@Path("/products") +@Produces("application/json") +@Consumes("application/json") +public interface ProductServiceClient { + + @GET + @Path("/{id}") + Product getProductById(@PathParam("id") Long id); +} +---- + +*Explanation* + +. The `@Produces("application/json")` annotation specifies that the client expects JSON responses. This determines the value of the `Accept` header in HTTP requests. + +. The `@Consumes("application/json")` annotation specifies that the client sends JSON requests. This determines the value of the `Content-Type` header of the request. + +. By default the media type `"application/json"` is used if `@Produces` and `@Consumes` are not explicitly set. + +. MicroProfile Rest Client automatically serializes Java objects to JSON and deserializes responses into Product DTO (Data Transfer Object) Java object. + +=== Error Handling + +Effective error handling is crucial when consuming remote RESTful services. MicroProfile Rest Client provides a structured approach to error handling by mapping HTTP responses to exceptions using the `ResponseExceptionMapper` interface. + +This mechanism allows developers to: + +. Convert specific HTTP response codes into custom exceptions. +. Customize exception handling behavior at runtime. +. Automatically throw mapped exceptions in client invocations. + +==== Using `ResponseExceptionMapper` interface + +The `ResponseExceptionMapper` interface allows mapping an HTTP Response object to a `Throwable` (custom exception). This improves error handling by ensuring meaningful exceptions are thrown instead of manually checking response codes. + +*How it Works* + +. *Scanning and Prioritizing Exception Mappers*: When a client method is invoked, the runtime scans all registered `ResponseExceptionMapper` implementations. Mappers are then sorted in ascending order of priority, determined by the `@Priority` annotation. The mapper with the lowest numeric priority value is checked first. + +. *Handling Responses*: The `handles(int status, MultivaluedMap headers)` method determines whether a mapper should handle a given response. By default, it handles responses with status code 400 or higher, but we can override this behavior. + +. *Converting the Response to an Exception*: The `toThrowable(Response response)` method converts a response into a `Throwable` (exception). Checked exceptions are only thrown if the client method declares that it throws that type of exception of its superclass. Unchecked exceptions (`RuntimeException`) are always thrown. + +Example: + +[source, java] +---- +package io.microprofile.tutorial.inventory.client; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; + +@RegisterRestClient(configKey = "product-service") +@RegisterProvider(ProductServiceResponseExceptionMapper.class) +@Path("/products") +public interface ProductServiceClient extends AutoCloseable { + + @GET + @Path("/{id}") + Response getProductById(@PathParam("id") Long id); +} +---- + +Explanation: + +. The REST client interface defines an endpoint for retrieving products. +. The `@RegisterProvider` annotation registers `ProductServiceResponseExceptionMapper`, ensuring custom exception handling. + +And below is the corresponding `ResponseExceptionMapper`: + +[source, java] +---- +package io.microprofile.tutorial.inventory.client; + +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; +import io.microprofile.tutorial.inventory.dto.ProductNotFoundException; + +public class ProductServiceResponseExceptionMapper implements ResponseExceptionMapper { + + @Override + public Throwable toThrowable(Response response) { + if (response.getStatus() == 404) { + return new ProductNotFoundException("Product not found"); + } + return new Exception("An unexpected error occurred"); + } +} +---- +*Explanation:* + +If the response status code is `404`, a `ProductNotFoundException` is thrown. Otherwise, a generic exception is returned. + +=== Using the `RestClientBuilder` Class + +While *CDI-based injection* is commonly used for REST clients in MicroProfile, programmatic creation using the `RestClientBuilder` class is beneficial when CDI is unavailable or when dynamic client instantiation is required. This builder provides a *fluent API* for configuring and constructing REST client proxies without relying on constructors that require numerous arguments. + +Using `RestClientBuilder` simplifies object creation, improves code readability, and supports *method chaining*, where each configuration method returns the builder instance itself. + +==== Example: Inventory Service Calls Product Service + +In the MicroProfile Ecommerce Store, the `InventoryService` must verify whether a product exists before checking or updating inventory. This interaction can be handled by calling the `ProductService` using a REST client interface. + +[source,java] +---- +package io.microprofile.tutorial.store.inventory.service; + +import io.microprofile.tutorial.store.inventory.client.ProductServiceClient; +import io.microprofile.tutorial.store.product.entity.Product; +import org.eclipse.microprofile.rest.client.RestClientBuilder; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +public class InventoryService { + + public boolean isProductAvailable(Long productId) { + URI productApiUri = URI.create("http://localhost:8080/api"); + + try (ProductServiceClient productClient = RestClientBuilder.newBuilder() + .baseUri(productApiUri) + .connectTimeout(3, TimeUnit.SECONDS) + .readTimeout(5, TimeUnit.SECONDS) + .build(ProductServiceClient.class)) { + + Product product = productClient.getProductById(productId); + return product != null; + + } catch (Exception e) { + // Log exception (omitted for brevity) + return false; + } + } +} +---- + +==== Explanation + +- The `isProductAvailable()` method accepts a product ID and returns `true` if the product exists in the catalog. +- A `URI` object is created pointing to the base path of the ProductService API using `URI.create()`. +- A `ProductServiceClient` instance is created using the builder pattern inside a `try-with-resource` block: + * `newBuilder()` initializes the client builder. + * `baseUri()` sets the root endpoint of the target service. + * `connectTimeout()` and `readTimeout()` define connection and read timeouts respectively. + * `build()` finalizes and returns the configured client proxy. +- Because `ProductServiceClient` extends `AutoCloseable`, the try-with-resources block ensures that the client is automatically closed after the operation, preventing resource leaks. +- If a `Product` object is successfully returned, `true` is returned. +- Any exceptions are caught and handled appropriately, returning `false` in case of failure. + +This approach is especially useful for *utility services*, *batch jobs*, or environments where REST client configuration must be *dynamic or conditional*, and manual client lifecycle management is necessary. + +> Tip: When building MicroProfile REST clients programmatically (using `RestClientBuilder`), ensure that your client interface extends `AutoCloseable` and uses try-with-resources to release resources automatically. + +=== Conclusion + +The MicroProfile Rest Client provides a declarative, type-safe, and efficient mechanism for interacting with RESTful services in Java microservices. It reduces boilerplate code and lets developers focus on core business logic while still offering fine-grained control through features like `RestClientBuilder`. + +By integrating seamlessly with other MicroProfile specifications—such as *Config*, *Fault Tolerance*, and *JWT Authentication*—the Rest Client helps enhance the *security*, *resilience*, and *maintainability* of cloud-native applications. + +==== Key Takeaways + +- Removes boilerplate HTTP code, improving clarity and maintainability. +- Automatically handles JSON serialization and deserialization. +- Supports *CDI injection* for managed client lifecycles. +- Integrates with *Fault Tolerance* for retries, timeouts, and circuit breakers. +- Enhances *security* through header propagation and authentication mechanisms. + +With MicroProfile Rest Client, building robust and maintainable microservices that communicate over REST becomes *simpler*, *more flexible*, and *more powerful*. This concludes the MicroProfile tutorial. You are now equipped with the foundational knowledge to build robust, cloud-native microservices using the MicroProfile specification. Thank you for following along, and happy coding! diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc new file mode 100644 index 00000000..35bec02c --- /dev/null +++ b/modules/ROOT/pages/index.adoc @@ -0,0 +1,154 @@ += MicroProfile API Tutorial +:id: index +:doctype: book + +---- +MicroProfile API Tutorial + +Version: 6.1 + +Status: Draft +---- + +== Legal + +Copyright (c) 2024 Contributors to the Eclipse Foundation + +See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership. + +Licensed under the Apache License, Version 2.0 (the "License"); +You may not use this file except in compliance with the License. +You may obtain a copy of the License at + +---- + http://www.apache.org/licenses/LICENSE-2.0 +---- + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. + +== Preface + +=== About this Tutorial + +In this tutorial, you will learn how to use the features of the MicroProfile Platform by building a microservices-based e-commerce application named "MicroProfile e-Commerce". The tutorial will cover using MicroProfile APIs such as Config, REST Client, JWT, Fault Tolerance, and Metrics to build efficient, scalable, and resilient microservices for cloud-native applications. We aim to provide a comprehensive overview and hands-on knowledge about using MicroProfile APIs. + +=== Who is this Tutorial for + +This tutorial caters to software professionals, from beginners to senior developers, engineering managers, and architects, to adeptly utilize MicroProfile in real-world projects. + +=== What will be Covered + +First, an overview of the MicroProfile project is presented, followed by detailed sections on each specification, complete with thoroughly tested and updated code samples. + +=== Project + +In this tutorial, you will learn to build a microservices-based e-commerce application called "MicroProfile e-Commerce". The app will demonstrate the use of MicroProfile APIs for developing an application based on microservices and cloud-native architecture. + +It would include multiple microservices, each serving a different purpose and highlighting different aspects of MicroProfile. Java developers can use this adaptation as a practical case study to implement MicroProfile APIs in real-world applications. + +The MicroProfile e-Commerce application compirses of multiple microservices, among the key ones are as below: + +- **Product Catalog**: This service acts as the central repository for all product-related information, including detailed descriptions, and pricing, and inventory levels. It provides APIs for fetching product details efficiently for the other microservices, such as the Shopping Cart. This microservice is vital for updating product data, ensuring data consistency and accuracy across the e-Commerce platform. + +- **The Shopping Cart**: This service allows users to add or remove products from their shopping cart. It communicates with the Product Catalog Microservice to access up-to-date product information. It handles the storage and management of cart items for each user, including the calculation of cart totals with applicable discounts or promotions. This microservice plays interfaces with the Checkout microservice to initiate the order processing. + +- **User Management**: This service is responsible for user account management, handles registration, login, and account updates securely using JWT tokens. It is essential for personalizing the user experience and safeguarding user information. + +- **Order Processing**: This service manages the entire order process, from collecting shipping information and confirming order details to initiating payment processing. This microservice ensures a seamless transition from shopping to order completion. + +- **Payment**: Dedicated to processing payments, this microservice interacts with external payment gateways to securely handle transactions. It receives payment instructions from the Checkout microservice, executes the payment process, and confirms transaction outcomes. This microservice is crucial for ensuring financial transactions are conducted securely and efficiently, maintaining the integrity of the payment process. + +- **Inventory**: This service is dedicated to monitoring and managing inventory levels. It tracks product availability, updates inventory in real-time as sales occu, and provides restocking alerts. By integrating with the Product Catalog and Checkout microservices, it ensures that product availability is accurately reflected on the platform and that orders are only placed for in-stock items. This microservice is crucial for maintaining optimal inventory levels and preventing stockouts, thereby enhancing the customer shopping experience. + +- **Shipping**: This microservice is responsible for managing the logistics of order delivery. It receives order details and shipping information from the Order Processing Microservice, ensuring that orders are shipped to customers in a timely and efficient manner. The Shipping Microservice plays a critical role in the post-purchase customer experience, managing expectations and communication regarding order delivery. + +:imagesdir: ../assets/images +:figure-caption: Figure +.MicroProfile e-Commerce Application +image::figureFM-1.png[MicroProfile e-Commerce Application] + +As you can see in the above figure, together these microservices form a robust and flexible e-Commerce application architecture, enabling scalable, efficient, and secure online shopping experiences. + +=== Downloading the Code +The code examples in this tutorial are available at this link:https://www.github.com/eclipse/microprofile-tutorial[repo]. + +=== Prerequisites +MicroProfile uses the Java Platform, and are usually written in the Java programming language. +All the examples in this tutorial are written in Java. +If you're new to Java, spend some time getting up to speed on the language and platform; +a good place to start is https://dev.java/learn/[dev.java/learn]. + +Each topic in this tutorial provides some background information, +but in general, +we assume you have a basic understanding of RESTful Web Services. + +=== Learning Objectives + +* Understanding MicroProfile and Its Ecosystem: + +** Gain a solid understanding of what MicroProfile is and its role in modern cloud-native application development. + +** Learn about the evolution of MicroProfile and its relationship with Jakarta EE. + +** Understand how MicroProfile facilitates building microservices. + +* Hands-On Experience with Key MicroProfile APIs: + +** Learn to implement Config, Health, Metrics, JWT Authentication, Fault Tolerance, Rest Client, and other MicroProfile APIs. + +** Understand how to apply these APIs in practical scenarios through the Duke's Forest application case study. + +* Building Resilient and Scalable Services: + +** Master techniques for developing resilient services using fault tolerance and health checks. + +* Securing Microservices: + +** Learn the intricacies of securing microservices using MicroProfile JWT and Security API. + +* Effective Data Management in Microservices: + +** Understand the role of JPA and JSON-B in MicroProfile for handling data operations in microservices. + +* Monitoring and Tracing: + +** Implement monitoring strategies using MicroProfile Metrics. + +** Learn to trace microservice interactions with OpenTracing for enhanced observability. + +* Collaborative Learning and Community Engagement: + +** Participate in Q&A sessions, forums, and interactive discussions. + +** Engage with the MicroProfile community for continuous learning and staying updated with the latest trends. + +By the end of this tutorial readers will gain the knowledge and skills necessary to design, develop, and deploy robust microservices using MicroProfile, preparing them for advanced roles in software development and architecture in cloud-native environments. + +== Conventions +[width="99%",cols="20%,38%,37%"] +|=== +|Convention |Meaning |Example + +|*Boldface* |Boldface type indicates a term defined in text or graphical user interface elements associated with an action. |A *cache* is a copy stored locally. + +From the *File* menu, choose *Open Project*. + +|`Monospace` |Monospace type indicates the names of files and directories, commands within a paragraph, URLs, code in examples, text that appears on the screen, or text that you enter. |Edit your `.login` file. + +Use `ls -a` to list all files. + +`_machine_name_% you have mail.` + +|_Italic_ |Italic type indicates book titles, emphasis, or placeholder variables for which you supply particular values. |Read Chapter 6 in the _User's Guide_. + +Do _not_ save the file. + +The command to remove a file is `rm _filename_`. +|=== diff --git a/modules/ROOT/pages/nav.adoc b/modules/ROOT/pages/nav.adoc new file mode 100644 index 00000000..aa22a6ad --- /dev/null +++ b/modules/ROOT/pages/nav.adoc @@ -0,0 +1,18 @@ +* xref:index.adoc[Preface] +* xref:chapter01/chapter01.adoc[Introduction to MicroProfile] +* xref:chapter02/chapter02-00.adoc[Getting Started with MicroProfile] +// * xref:chapter02/chapter02-01.adoc[Create a Java Project] +// * xref:chapter02/chapter02-02.adoc[Choosing Right Modules] +// * xref:chapter02/chapter02-03.adoc[Developing a RESTful Web Service] +// * xref:chapter02/chapter02-04.adoc[Testing your microservice] +// * xref:chapter02/chapter02-05.adoc[Package Structure] +// * xref:chapter02/chapter02-06.adoc[Glossary] +* xref:chapter03/chapter03.adoc[Jakarta EE 10 Core Profile] +* xref:chapter04/chapter04.adoc[MicroProfile OpenAPI] +* xref:chapter05/chapter05.adoc[MicroProfile Configuration] +* xref:chapter06/chapter06.adoc[MicroProfile Health] +* xref:chapter07/chapter07.adoc[MicroProfile Metrics] +* xref:chapter08/chapter08.adoc[MicroProfile Fault Tolerance] +* xref:chapter09/index.adoc[MicroProfile Telemetry] +* xref:chapter10/chapter10.adoc[MicroProfile JWT] +* xref:chapter11/chapter11.adoc[MicroProfile Rest Client] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..3da83436 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,821 @@ +{ + "name": "microprofile-tutorial", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@antora/collector-extension": "^1.0.1", + "@antora/lunr-extension": "^1.0.0-alpha.10", + "@antora/pdf-extension": "^1.0.0-alpha.11", + "@asciidoctor/tabs": "^1.0.0-beta.6", + "asciidoctor-kroki": "^0.18.1", + "asciidoctor-plantuml": "^1.5.0" + } + }, + "node_modules/@antora/assembler": { + "version": "1.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@antora/assembler/-/assembler-1.0.0-alpha.11.tgz", + "integrity": "sha512-9Ssvyhc4Q6q5oJQpXTXNHHcrJKUi9vvDcWTurLiZhP5GvCMYgukxXV3vkW/qye1+ePGPlDgtNQcgVJMaymh25A==", + "license": "MPL-2.0", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "@antora/run-command-helper": "~1.0", + "@asciidoctor/reducer": "~1.1", + "braces": "~3.0", + "js-yaml": "~4.1", + "picomatch": "~3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/assembler/node_modules/@antora/expand-path-helper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antora/expand-path-helper/-/expand-path-helper-2.0.0.tgz", + "integrity": "sha512-CSMBGC+tI21VS2kGW3PV7T2kQTM5eT3f2GTPVLttwaNYbNxDve08en/huzszHJfxo11CcEs26Ostr0F2c1QqeA==", + "license": "MPL-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@antora/collector-extension": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@antora/collector-extension/-/collector-extension-1.0.1.tgz", + "integrity": "sha512-Bq6s2ZN5VESJ3/skEeytfCeBRD4Z/K06dWQFPTMFUn3+nzaA/NDeQ1x16YPk491DeOSg4dXcW0CerwVqdL3zPQ==", + "license": "MPL-2.0", + "dependencies": { + "@antora/expand-path-helper": "~3.0", + "@antora/run-command-helper": "~1.0", + "cache-directory": "~2.0", + "fast-glob": "~3.3", + "js-yaml": "~4.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/expand-path-helper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@antora/expand-path-helper/-/expand-path-helper-3.0.0.tgz", + "integrity": "sha512-7PdEIhk97v85/CSm3HynCsX14TR6oIVz1s233nNLsiWubE8tTnpPt4sNRJR+hpmIZ6Bx9c6QDp3XIoiyu/WYYA==", + "license": "MPL-2.0", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/lunr-extension": { + "version": "1.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@antora/lunr-extension/-/lunr-extension-1.0.0-alpha.10.tgz", + "integrity": "sha512-YunvJ3D/Q/GfIgOvmoFvL6NrKLL3TBN2UiemIDCquvWl6z0Jlonb5w3GkT/CKhtnGWXDPOMOJ7M4fVOKjMOHZw==", + "license": "MPL-2.0", + "dependencies": { + "htmlparser2": "~9.1", + "lunr": "~2.3", + "lunr-languages": "~1.10" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/pdf-extension": { + "version": "1.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@antora/pdf-extension/-/pdf-extension-1.0.0-alpha.11.tgz", + "integrity": "sha512-c2EyOhWSjZt4tWWkDnScj3yExclkPUH8Hsp3kk9HWbDjNumptdNXdFtF5HaDVDlrDzyEOSORiqru1zbrIaYJdw==", + "license": "MPL-2.0", + "dependencies": { + "@antora/assembler": "1.0.0-alpha.11" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/run-command-helper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@antora/run-command-helper/-/run-command-helper-1.0.1.tgz", + "integrity": "sha512-VvHPfatfHH/jW+u1LGYKsGoFXEO5KkSQY6lW66173bqHmImNsBVpQj+YQP32lcKoE0X3G3fefDFnHGzKu0kD8g==", + "license": "MPL-2.0", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@asciidoctor/core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-3.0.4.tgz", + "integrity": "sha512-41SDMi7iRRBViPe0L6VWFTe55bv6HEOJeRqMj5+E5wB1YPdUPuTucL4UAESPZM6OWmn4t/5qM5LusXomFUVwVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@asciidoctor/opal-runtime": "3.0.1", + "unxhr": "1.2.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + } + }, + "node_modules/@asciidoctor/opal-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@asciidoctor/opal-runtime/-/opal-runtime-3.0.1.tgz", + "integrity": "sha512-iW7ACahOG0zZft4A/4CqDcc7JX+fWRNjV5tFAVkNCzwZD+EnFolPaUOPYt8jzadc0+Bgd80cQTtRMQnaaV1kkg==", + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "8.1.0", + "unxhr": "1.2.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@asciidoctor/reducer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@asciidoctor/reducer/-/reducer-1.1.1.tgz", + "integrity": "sha512-OmSBStkPvVPdlxlR85bXk23fiUQPdvNcweDXtZYHWJ2b+RTNrDA5sO2gj+uig9UryspeKLdeBb+rgE7vXhcdOw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@asciidoctor/tabs": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@asciidoctor/tabs/-/tabs-1.0.0-beta.6.tgz", + "integrity": "sha512-gGZnW7UfRXnbiyKNd9PpGKtSuD8+DsqaaTSbQ1dHVkZ76NaolLhdQg8RW6/xqN3pX1vWZEcF4e81+Oe9rNRWxg==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/asciidoctor-kroki": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/asciidoctor-kroki/-/asciidoctor-kroki-0.18.1.tgz", + "integrity": "sha512-eQxbBCaPTbyNoJtk62Gp+6h4LlJp2147g7eS0QIVjqaLpFa8sseH0BlMiBoATrJUYv1w3nR+FTzvloBJ/MioYg==", + "license": "MIT", + "dependencies": { + "json5": "2.2.3", + "mkdirp": "2.1.3", + "pako": "2.1.0", + "rusha": "0.8.14", + "unxhr": "1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@asciidoctor/core": ">=2.2 <4.0" + } + }, + "node_modules/asciidoctor-plantuml": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/asciidoctor-plantuml/-/asciidoctor-plantuml-1.5.0.tgz", + "integrity": "sha512-YsiDyrr0iBkFrDkqSXujbZNS1YkhiKl6sIo3OB6yydSozrahXYX2LdU7pmQPUwe8kYeCy3L2G39O4kQc68mCew==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "ISC", + "dependencies": { + "asciidoctor.js": "^1.5.9", + "mkdirp": "^0.5.1", + "plantuml-encoder": "^1.2.5", + "rusha": "^0.8.13", + "unxhr": "1.0.1" + } + }, + "node_modules/asciidoctor-plantuml/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/asciidoctor-plantuml/node_modules/unxhr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.0.1.tgz", + "integrity": "sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg==", + "license": "MIT", + "engines": { + "node": ">=8.11" + } + }, + "node_modules/asciidoctor.js": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/asciidoctor.js/-/asciidoctor.js-1.5.9.tgz", + "integrity": "sha512-k5JgwyV82TsiCpnYbDPReuHhzf/vRUt6NaZ+OGywkDDGeGG/CPfvN2Gd1MJ0iIZKDyuk4iJHOdY/2x1KBrWMzA==", + "deprecated": "Package no longer supported. Replaced by @asciidoctor/core", + "license": "MIT", + "dependencies": { + "opal-runtime": "1.0.11" + }, + "engines": { + "node": ">=8.11", + "npm": ">=5.0.0", + "yarn": ">=1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-directory": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cache-directory/-/cache-directory-2.0.0.tgz", + "integrity": "sha512-7YKEapH+2Uikde8hySyfobXBqPKULDyHNl/lhKm7cKf/GJFdG/tU/WpLrOg2y9aUrQrWUilYqawFIiGJPS6gDA==", + "license": "LGPL-3.0+", + "dependencies": { + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "peer": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "license": "MIT" + }, + "node_modules/lunr-languages": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.10.0.tgz", + "integrity": "sha512-BBjKKcwrieJlzwwc9M5H/MRXGJ2qyOSDx/NXYiwkuKjiLOOoouh0WsDzeqcLoUWcX31y7i8sb8IgsZKObdUCkw==", + "license": "MPL-1.1" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.3.tgz", + "integrity": "sha512-sjAkg21peAG9HS+Dkx7hlG9Ztx7HLeKnvB3NQRcu/mltCVmvkF0pisbiTSfDVYTT86XEfZrTUosLdZLStquZUw==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opal-runtime": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/opal-runtime/-/opal-runtime-1.0.11.tgz", + "integrity": "sha512-L+6pnRvXPlDtbamBRnJAnB9mEMXmsIQ/b+0r/2xJ5/n/nxheEkLo+Pm5QNQ08LEbEN9TI6/kedhIspqRRu6tXA==", + "license": "MIT", + "dependencies": { + "glob": "6.0.4", + "xmlhttprequest": "1.8.0" + }, + "engines": { + "node": ">=8.11" + } + }, + "node_modules/opal-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/opal-runtime/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/opal-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plantuml-encoder": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz", + "integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/unxhr": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.2.0.tgz", + "integrity": "sha512-6cGpm8NFXPD9QbSNx0cD2giy7teZ6xOkCUH3U89WKVkL9N9rBrWjlCwhR94Re18ZlAop4MOc3WU1M3Hv/bgpIw==", + "license": "MIT", + "engines": { + "node": ">=8.11" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..6eb1e1df --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "@antora/collector-extension": "^1.0.1", + "@antora/lunr-extension": "^1.0.0-alpha.10", + "@antora/pdf-extension": "^1.0.0-alpha.11", + "@asciidoctor/tabs": "^1.0.0-beta.6", + "asciidoctor-kroki": "^0.18.1", + "asciidoctor-plantuml": "^1.5.0" + } +} diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 00000000..d11bffc9 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,13 @@ +site: + title: "MicroProfile Tutorial" + url: "https://microprofile.io/tutorial" + start_page: "index.adoc" +content: + sources: + - url: "https://github.com/ttelang/microprofile-tutorial.git" + branches: main + start_path: / +ui: + bundle: + url: "https://gitlab.com/antora/antora-ui-default/-/archive/master/antora-ui-default-master.zip" + snapshot: true diff --git a/redirect.html b/redirect.html new file mode 100644 index 00000000..a0c9abb5 --- /dev/null +++ b/redirect.html @@ -0,0 +1,18 @@ + + + + + + + +

Redirecting to MicroProfile Tutorial...

+ + diff --git a/site.yml b/site.yml new file mode 100644 index 00000000..e81e67a6 --- /dev/null +++ b/site.yml @@ -0,0 +1,18 @@ +site: + title: MicroProfile Tutorial + start_page: microprofile-tutorial::index.adoc + +content: + sources: + - url: . + start_path: . + +ui: + bundle: + url: https://github.com/microprofile/microprofile-tutorial-ui/releases/tag/latest/ui-bundle.zip +output: + dir: ./build/site + +runtime: + log: + level: debug \ No newline at end of file diff --git a/supplemental-ui/img/favicon.png b/supplemental-ui/img/favicon.png new file mode 100644 index 00000000..1276abca Binary files /dev/null and b/supplemental-ui/img/favicon.png differ diff --git a/supplemental-ui/img/favicon.svg b/supplemental-ui/img/favicon.svg new file mode 100644 index 00000000..086680cd --- /dev/null +++ b/supplemental-ui/img/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/supplemental-ui/partials/head-meta.hbs b/supplemental-ui/partials/head-meta.hbs new file mode 100644 index 00000000..ee348b5d --- /dev/null +++ b/supplemental-ui/partials/head-meta.hbs @@ -0,0 +1,3 @@ + + + diff --git a/update-repo-url.sh b/update-repo-url.sh new file mode 100755 index 00000000..8d8d731f --- /dev/null +++ b/update-repo-url.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# update-repo-url.sh - Updates repository URLs in configuration files +# This script detects the Git repository URL and branch and updates them in configuration files + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Detecting Git repository information...${NC}" + +# Get the remote repository URL +REPO_URL=$(git remote get-url origin 2>/dev/null) +if [ -z "$REPO_URL" ]; then + echo -e "${YELLOW}Warning: Could not determine remote repository URL.${NC}" + echo -e "${YELLOW}Using default URL: https://github.com/ttelang/microprofile-tutorial${NC}" + REPO_URL="https://github.com/ttelang/microprofile-tutorial" +else + # Convert SSH URL to HTTPS URL if needed + if [[ $REPO_URL == git@github.com:* ]]; then + REPO_URL="https://github.com/${REPO_URL#git@github.com:}" + fi + # Remove .git suffix if present + REPO_URL=${REPO_URL%.git} + echo -e "${GREEN}Repository URL: ${REPO_URL}${NC}" +fi + +# Get the current branch +BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) +if [ -z "$BRANCH" ]; then + echo -e "${YELLOW}Warning: Could not determine current branch.${NC}" + echo -e "${YELLOW}Using default branch: main${NC}" + BRANCH="main" +else + echo -e "${GREEN}Current branch: ${BRANCH}${NC}" +fi + +# Update antora.yml with the correct edit_url +echo -e "${BLUE}Updating antora.yml with repository information...${NC}" +EDIT_URL="${REPO_URL}/edit/${BRANCH}/modules/ROOT/pages/{path}" + +# Use sed to update the edit_url in antora.yml +if [ -f "antora.yml" ]; then + # Check if edit_url already exists + if grep -q "^edit_url:" antora.yml; then + # Replace existing edit_url + sed -i "s|^edit_url:.*$|edit_url: ${EDIT_URL}|" antora.yml + else + # Add edit_url after version line + sed -i "/^version:/a edit_url: ${EDIT_URL}" antora.yml + fi + echo -e "${GREEN}✓${NC} Updated edit_url in antora.yml to: ${YELLOW}${EDIT_URL}${NC}" +else + echo -e "${RED}Error: antora.yml not found${NC}" +fi + +# Update fix-edit-links.sh with the correct REPO_URL and BRANCH +echo -e "${BLUE}Updating fix-edit-links.sh with repository information...${NC}" +if [ -f "fix-edit-links.sh" ]; then + # Update REPO_URL + sed -i "s|^REPO_URL=.*$|REPO_URL=\"${REPO_URL}\"|" fix-edit-links.sh + # Update BRANCH + sed -i "s|^BRANCH=.*$|BRANCH=\"${BRANCH}\"|" fix-edit-links.sh + echo -e "${GREEN}✓${NC} Updated repository information in fix-edit-links.sh" +else + echo -e "${RED}Error: fix-edit-links.sh not found${NC}" +fi + +echo -e "\n${GREEN}Repository information has been updated in configuration files.${NC}" +echo -e "You can now build the documentation with:" +echo -e " ${YELLOW}antora antora-assembler.yml && ./fix-edit-links.sh${NC}"