diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..f0fb009 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,42 @@ +version: 2.1 + +commands: + build_and_test: + parameters: + reports-dir: + type: string + default: "build/reports/test_results" + steps: + - checkout + - run: + name: Download dependencies + command: ./bld download + - run: + name: Compile source + command: ./bld compile + - run: + name: Run tests + command: ./bld jacoco -reports-dir=<< parameters.reports-dir >> + - store_test_results: + path: << parameters.reports-dir >> + - store_artifacts: + path: build/reports/jacoco/test/html +jobs: + bld_jdk17: + docker: + - image: cimg/openjdk:17.0 + steps: + - build_and_test + + bld_jdk21: + docker: + - image: cimg/openjdk:21.0 + steps: + - build_and_test + +workflows: + bld: + jobs: + - bld_jdk17 + - bld_jdk21 + diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml new file mode 100644 index 0000000..057289d --- /dev/null +++ b/.github/workflows/bld.yml @@ -0,0 +1,53 @@ +name: bld-ci + +on: [ push, pull_request, workflow_dispatch ] + +jobs: + build-bld-project: + env: + COVERAGE_JDK: "17" + + strategy: + matrix: + java-version: [ 17, 21, 24 ] + os: [ ubuntu-latest, windows-latest, macos-latest ] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout source repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + distribution: "zulu" + java-version: ${{ matrix.java-version }} + + - name: Download dependencies + run: ./bld download + + - name: Compile source + run: ./bld compile + + - name: Run tests + run: ./bld jacoco + + - name: Remove pom.xml + if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest' + run: rm -rf pom.xml + + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000..9410c34 --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1 @@ +future-release=1.1.2 diff --git a/.gitignore b/.gitignore index d7290a0..06c4940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,58 @@ -**/.idea/dictionaries -**/.idea/gradle.xml -**/.idea/libraries -**/.idea/tasks.xml -**/.idea/workspace.xml -*.iws -.DS_Store -.classpath .gradle -.nb-gradle -.project -.settings -/bin -/build -/deploy -/dist -/gen -/local.properties -/out -/proguard-project.txt -/project.properties -/test-output -Thumbs.db -ehthumbs.db \ No newline at end of file +.DS_Store +build +lib/bld/** +!lib/bld/bld-wrapper.properties +!lib/bld/bld-wrapper.jar +lib/compile/ +lib/runtime/ +lib/standalone/ +lib/test/ + +# IDEA ignores + +# User-specific +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Editor-based Rest Client +.idea/httpRequests + +target +local.properties diff --git a/.idea/app.iml b/.idea/app.iml new file mode 100644 index 0000000..787b59b --- /dev/null +++ b/.idea/app.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/bld.iml b/.idea/bld.iml new file mode 100644 index 0000000..e63e11e --- /dev/null +++ b/.idea/bld.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/bld.xml b/.idea/bld.xml new file mode 100644 index 0000000..6600cee --- /dev/null +++ b/.idea/bld.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..d91f848 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/Erik_s_Copyright.xml b/.idea/copyright/Erik_s_Copyright.xml new file mode 100644 index 0000000..1d3bbab --- /dev/null +++ b/.idea/copyright/Erik_s_Copyright.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..1419e40 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1e01b48 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/libraries/bld.xml b/.idea/libraries/bld.xml new file mode 100644 index 0000000..153a060 --- /dev/null +++ b/.idea/libraries/bld.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/.idea/libraries/compile.xml b/.idea/libraries/compile.xml new file mode 100644 index 0000000..99cc0c0 --- /dev/null +++ b/.idea/libraries/compile.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/runtime.xml b/.idea/libraries/runtime.xml new file mode 100644 index 0000000..d4069f2 --- /dev/null +++ b/.idea/libraries/runtime.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/test.xml b/.idea/libraries/test.xml new file mode 100644 index 0000000..57ed5ef --- /dev/null +++ b/.idea/libraries/test.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..71f1817 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..55adcb9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run Tests.xml b/.idea/runConfigurations/Run Tests.xml new file mode 100644 index 0000000..99dc892 --- /dev/null +++ b/.idea/runConfigurations/Run Tests.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..32bf99b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Run Tests", + "request": "launch", + "mainClass": "net.thauvin.erik.httpstatus.HttpStatusTest" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3ef096f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "java.project.sourcePaths": [ + "src/main/java", + "src/main/resources", + "src/test/java", + "src/bld/java", + "src/bld/resources" + ], + "java.configuration.updateBuildConfiguration": "automatic", + "java.project.referencedLibraries": [ + "${HOME}/.bld/dist/bld-2.2.1.jar", + "lib/**/*.jar", + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..159135a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,62 @@ +# Changelog + +## [1.1.1](https://github.com/ethauvin/httpstatus/tree/1.1.1) (2024-06-07) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.1.0...1.1.1) + +**Implemented enhancements:** + +- Sort command line output [\#10](https://github.com/ethauvin/HttpStatus/issues/10) +- Update reasons properties status codes [\#9](https://github.com/ethauvin/HttpStatus/issues/9) + +## [1.1.0](https://github.com/ethauvin/httpstatus/tree/1.1.0) (2023-09-29) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0.5...1.1.0) + +**Implemented enhancements:** + +- Print status code by response classes [\#8](https://github.com/ethauvin/HttpStatus/issues/8) +- Move to Jakarta EE [\#7](https://github.com/ethauvin/HttpStatus/issues/7) +- Only use XML character entities in Utils.escapeXml\(\) [\#6](https://github.com/ethauvin/HttpStatus/issues/6) +- Implement a StatusCode bean to check the status code. [\#5](https://github.com/ethauvin/HttpStatus/issues/5) + +## [1.0.5](https://github.com/ethauvin/httpstatus/tree/1.0.5) (2021-03-20) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0.4...1.0.5) + +**Implemented enhancements:** + +- Retrieve the error message from the request, if available. [\#4](https://github.com/ethauvin/HttpStatus/issues/4) +- Add Unofficial Codes [\#3](https://github.com/ethauvin/HttpStatus/issues/3) + +## [1.0.4](https://github.com/ethauvin/httpstatus/tree/1.0.4) (2019-05-08) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0.3...1.0.4) + +**Implemented enhancements:** + +- Implement all INA HTTP status codes [\#1](https://github.com/ethauvin/HttpStatus/issues/1) + +**Fixed bugs:** + +- hs:reason always outputs the default value. [\#2](https://github.com/ethauvin/HttpStatus/issues/2) + +## [1.0.3](https://github.com/ethauvin/httpstatus/tree/1.0.3) (2016-01-22) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0.2...1.0.3) + +## [1.0.2](https://github.com/ethauvin/httpstatus/tree/1.0.2) (2016-01-21) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0.1...1.0.2) + +## [1.0.1](https://github.com/ethauvin/httpstatus/tree/1.0.1) (2015-12-16) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/1.0...1.0.1) + +## [1.0](https://github.com/ethauvin/httpstatus/tree/1.0) (2015-12-04) + +[Full Changelog](https://github.com/ethauvin/httpstatus/compare/ad982eff1b27c31b54df4abae00ac2d4065543d6...1.0) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/HttpStatus.iml b/HttpStatus.iml deleted file mode 100644 index f05d6c7..0000000 --- a/HttpStatus.iml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/HttpStatus.ipr b/HttpStatus.ipr deleted file mode 100644 index c5332d5..0000000 --- a/HttpStatus.ipr +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..79de5a8 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2015-2025, Erik C. Thauvin (erik@thauvin.net) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of this project nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 7e6d876..40bc87c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,37 @@ -# HttpStatus JSP Tag Library +# HttpStatus JSP Tag Library -A simple JSP Tag Library to display the reason of [HTTP status codes](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) in JSP error pages. +[![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](http://opensource.org/licenses/BSD-3-Clause) +[![Java](https://img.shields.io/badge/java-17%2B-blue)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) +[![bld](https://img.shields.io/badge/2.2.1-FA9052?label=bld&labelColor=2392FF)](https://rife2.com/bld) +[![Release](https://img.shields.io/github/release/ethauvin/httpstatus.svg)](https://github.com/ethauvin/httpstatus/releases/latest) +[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/net.thauvin.erik.httpstatus/httpstatus.svg?label=sanpshot&server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/httpstatus/httpstatus/) +[![Maven Central](https://img.shields.io/maven-central/v/net.thauvin.erik.httpstatus/httpstatus.svg?color=blue)](https://central.sonatype.com/artifact/net.thauvin.erik.httpstatus/httpstatus) ----- +[![Known Vulnerabilities](https://snyk.io/test/github/ethauvin/httpstatus/badge.svg?targetFile=pom.xml)](https://snyk.io/test/github/ethauvin/httpstatus?targetFile=pom.xml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ethauvin_HttpStatus&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ethauvin_HttpStatus) +[![GitHub CI](https://github.com/ethauvin/httpstatus/actions/workflows/bld.yml/badge.svg)](https://github.com/ethauvin/httpstatus/actions/workflows/bld.yml) +[![CircleCI](https://circleci.com/gh/ethauvin/HttpStatus/tree/master.svg?style=shield)](https://circleci.com/gh/ethauvin/HttpStatus/tree/master) + +A simple [JSP](http://www.oracle.com/technetwork/java/javaee/jsp/index.html) Tag Library to display the [code](#hscode), [reason](#hsreason), [cause](#hscode) and/or [message](#hsmessage) for [HTTP status codes](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) in JSP error pages. + +## Table of Contents + +- [Examples](#examples) +- [Usage](#usage) + - [Gradle](#gradle) + - [bld](#bld) + - [Maven](#maven) +- [JSP Tags](#jsp-tags) + - [hs:cause](#hscause) + - [hs:code](#hscode) + - [hs:message](#hsmessage) + - [hs:reason](#hsreason) +- [StatusCode Bean](#statuscode-bean) +- [Reasons](#reasons) +- [Command Line Usage](#command-line-usage) +- [Contributing](#contributing) + +## Examples For example: @@ -14,6 +43,7 @@ For example:

Cause:
+Message:
... ``` @@ -24,13 +54,53 @@ or <%= Reasons.getReasonPhrase(pageContext.getErrorData().getStatusCode()) %> ``` -would display on a [501 status code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.2): +would display on a [501 status code](https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented): - Not Implemented +```console +Not Implemented +``` ----- +## Usage -## hs:cause +### [Gradle](https://gradle.org/) + +Include the following in your `build.gradle` file: + +```gradle +repositories { + mavenCentral() +} + +dependencies { + implementation 'net.thauvin.erik.httpstatus:httpstatus:1.1.1' +} +``` + +### [bld](https://rife2.com/bld) + +Include the following in your `bld` build file: + +```java +scope(compile).include( + dependency("net.thauvin.erik.httpstatus","httpstatus", version(1, 1, 0)) +); +``` + +### [Maven](http://maven.apache.org/) + +As a `Maven` artifact: + +```xml + + net.thauvin.erik.httpstatus + httpstatus + 1.1.1 + +``` + +## JSP Tags + +### hs:cause The `` tag displays the cause of current HTTP status code, if any. A shorthand for: @@ -40,107 +110,262 @@ The `` tag displays the cause of current HTTP status code, if any. A Optional attributes are: -Attribute | Description ------------ | ------------------------------------------------------------------------------------------- -`default` | The fallback value to output, if no cause is available. -`escapeXml` | Converts <,>,&,'," to their corresponding [entity codes](http://dev.w3.org/html5/html-author/charref). Value is true by default. +| Attribute | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `default` | The fallback value to output, if no cause is | +| `escapeXml` | Converts <, >, &, ', " to their corresponding [entity codes](http://dev.w3.org/html5/html-author/charref). Value is `true` by default. | + +### hs:code -## hs:code The `` tag displays the current HTTP status code, if any. A shorthand for: ```jsp <%= pageContext.getErrorData().getStatusCode() %> ``` -## hs:reason +### hs:message -The `` tag displays the reason for a HTTP status code, if any. Optional attributes are: +The `` tag displays the current error message, if any. A shorthand for: -Attribute | Description ------------ | ------------------------------------------------------------------------------------------- -`code` | The HTTP status error code. If not specified the current status code is used. -`default` | The fallback value to output, if no reason is available. -`escapeXml` | Converts <,>,&,'," to their corresponding [entity codes](http://dev.w3.org/html5/html-author/charref). Value is true by default. +```jsp +<%= request.getAttribute("javax.servlet.error.message") %> +``` -The reasons are defined in a [ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html) properties as follows: +Optional attributes are: -Status Code | Reason ------------ | ------------------------------- -`100` | Continue -`101` | Switching Protocols -`102` | Processing -`200` | OK -`201` | Created -`202` | Accepted -`203` | Non-Authoritative Information -`204` | No Content -`205` | Reset Content -`206` | Partial Content -`207` | Multi-Status -`208` | Already Reported -`226` | IM Used -`300` | Multiple Choices -`301` | Moved Permanently -`302` | Moved Temporarily -`303` | See Other -`304` | Not Modified -`305` | Use Proxy -`306` | Switch Proxy -`307` | Temporary Redirect -`308` | Permanent Redirect -`400` | Bad Request -`401` | Unauthorized -`402` | Payment Required -`403` | Forbidden -`404` | Not Found -`405` | Method Not Allowed -`406` | Not Acceptable -`407` | Proxy Authentication Required -`408` | Request Timeout -`409` | Conflict -`410` | Gone -`411` | Length Required -`412` | Precondition Failed -`413` | Request Entity Too Large -`414` | Request-URI Too Long -`415` | Unsupported Media Type -`416` | Requested Range Not Satisfiable -`417` | Expectation Failed -`418` | I'm A Teapot -`419` | Insufficient Space on Resource -`420` | Method Failure -`421` | Misdirected Request -`422` | Unprocessable Entity -`423` | Locked -`424` | Failed Dependency -`426` | Upgrade Required -`428` | Precondition Required -`429` | Too Many Requests -`431` | Request Header Fields Too Large -`440` | Login Timeout -`444` | No Response -`449` | Retry With -`450` | Blocked by Windows Parental Controls -`451` | Unavailable For Legal Reasons -`494` | Request Header Too Large -`495` | Cert Error -`496` | No Cert -`497` | HTTP to HTTPS -`498` | Token Expired/Invalid -`499` | Client Closed Request -`500` | Internal Server Error -`501` | Not Implemented -`502` | Bad Gateway -`503` | Service Unavailable -`504` | Gateway Timeout -`505` | HTTP Version Not Supported -`506` | Variant Also Negotiates -`507` | Insufficient Storage -`508` | Loop Detected -`509` | Bandwidth Limit Exceeded -`510` | Not Extended -`511` | Network Authentication Required -`520` | Unknown Error -`522` | Origin Connection Time-out -`598` | Network Read Timeout Error -`599` | Network Connect Timeout Error \ No newline at end of file +| Attribute | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `default` | The fallback value to output, if no error message is available. | +| `escapeXml` | Converts <, >, &, ', " to their corresponding [entity codes](http://dev.w3.org/html5/html-author/charref). Value is `true` by default. | + +### hs:reason + +The `` tag displays the reason for an HTTP status code, if any. Optional attributes are: + +| Attribute | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `default` | The fallback value to output, if no reason is available. | +| `code` | The HTTP status error code. If not specified the current status code is used. | +| `escapeXml` | Converts <, >, &, ', " to their corresponding [entity codes](http://dev.w3.org/html5/html-author/charref). Value is `true` by default. | + +## StatusCode Bean + +The `StatusCode` bean can be used to check the class of the status code error. For example, using the JSTL: + +```jsp +<%@ taglib prefix="hs" uri="http://erik.thauvin.net/taglibs/httpstatus" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + An error occurred on your side. () + + + An error occurred on our side. () + + +``` + +or in a Servlet: + +```java +import net.thauvin.erik.httpstatus.StatusCode; + +public class ExampleServlet extends HttpServlet { + public void doGet(HttpServletRequest request, HttpServletResponse response) { + var statusCode = new StatusCode( + (Integer) request.getAttribute("javax.servlet.error.status_code")); + if (statusCode.isError()) { + if (statusCode.isServerError()) { + var reason = statusCode.getReason(); + } else { + // ... + } + } + } +} +``` + +The `StatusCode` bean methods are: + +| Method | Description | +| --------------- | -------------------------------------------------------------------- | +| `getReason` | Returns the reason for the status code (eg: `Internal Server Error`) | +| `isClientError` | Checks if the status code is a client error. | +| `isError` | Checks if the status code is a server or client error. | +| `isInfo` | Checks if the status code is informational. | +| `isRedirect` | Checks if the status code is a redirect. | +| `isServerError` | Checks if the status code is a server error. | +| `isSuccess` | Checks if the status code is a success. (`OK`) | +| `isValid` | Checks if the status code is valid. | + +## Reasons + +The reasons are defined in a [ResourceBundle](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ResourceBundle.html) properties as follows: + +| Status Code | Reason | +| ----------- | ---------------------------------------------------------- | +| `100` | Continue | +| `101` | Switching Protocols | +| `102` | Processing | +| `103` | Early Hints | +| `110` | Response is Stale | +| `111` | Revalidation Failed | +| `112` | Disconnected Operation | +| `113` | Heuristic Expiration | +| `199` | Miscellaneous Warning | +| `200` | OK | +| `201` | Created | +| `202` | Accepted | +| `203` | Non-Authoritative Information | +| `204` | No Content | +| `205` | Reset Content | +| `206` | Partial Content | +| `207` | Multi-Status | +| `208` | Already Reported | +| `214` | Transformation Applied | +| `218` | This is fine | +| `226` | IM Used | +| `299` | Miscellaneous Persistent Warning | +| `300` | Multiple Choices | +| `301` | Moved Permanently | +| `302` | Found/Moved Temporarily | +| `303` | See Other | +| `304` | Not Modified | +| `305` | Use Proxy | +| `306` | Unused | +| `307` | Temporary Redirect | +| `308` | Permanent Redirect | +| `400` | Bad Request | +| `401` | Unauthorized | +| `402` | Payment Required | +| `403` | Forbidden | +| `404` | Not Found | +| `405` | Method Not Allowed | +| `406` | Not Acceptable | +| `407` | Proxy Authentication Required | +| `408` | Request Timeout | +| `409` | Conflict | +| `410` | Gone | +| `411` | Length Required | +| `412` | Precondition Failed | +| `413` | Payload Too Large | +| `414` | URI Too Long | +| `415` | Unsupported Media Type | +| `416` | Range Not Satisfiable | +| `417` | Expectation Failed | +| `418` | I'm A Teapot | +| `419` | Insufficient Space on Resource | +| `420` | Method Failure | +| `421` | Misdirected Request | +| `422` | Unprocessable Content | +| `423` | Locked | +| `424` | Failed Dependency | +| `425` | Too Early | +| `426` | Upgrade Required | +| `428` | Precondition Required | +| `429` | Too Many Requests | +| `430` | Request Header Fields Too Large | +| `431` | Request Header Fields Too Large | +| `440` | Login Timeout | +| `444` | No Response | +| `449` | Retry With | +| `450` | Blocked by Windows Parental Controls | +| `451` | Unavailable For Legal Reasons | +| `460` | Client Closed Connection Before Load Balancer Idle Timeout | +| `463` | X-Forwarded-For Header with More than 30 IP Addresses | +| `494` | Request Header Too Large | +| `495` | SSL Certificate Error | +| `496` | SSL Certificate Required | +| `497` | HTTP Request Sent to HTTPS Port | +| `498` | Token Expired/Invalid | +| `499` | Client Closed Request | +| `500` | Internal Server Error | +| `501` | Not Implemented | +| `502` | Bad Gateway | +| `503` | Service Unavailable | +| `504` | Gateway Timeout | +| `505` | HTTP Version Not Supported | +| `506` | Variant Also Negotiates | +| `507` | Insufficient Storage | +| `508` | Loop Detected | +| `509` | Bandwidth Limit Exceeded | +| `510` | Not Extended | +| `511` | Network Authentication Required | +| `520` | Unknown Error | +| `521` | Web Server Is Down | +| `522` | Connection Timed Out | +| `523` | Origin Is Unreachable | +| `524` | A Timeout Occurred | +| `525` | SSL Handshake Failed | +| `526` | Invalid SSL Certificate | +| `527` | Railgun Error | +| `529` | Site is overloaded | +| `530` | Site is frozen | +| `540` | Temporarily Disabled | +| `561` | Unauthorized | +| `598` | Network Read Timeout Error | +| `599` | Network Connect Timeout Error | +| `783` | Unexpected Token | + +## Command Line Usage + +You can query the reason phrase for status codes as follows: + +```console +$ java -jar httpstatus-1.1.1.jar 404 500 +404: Not Found +500: Internal Server Error +``` + +If no status code is specified, all will be printed: + +```console +$ java -jar httpstatus-1.1.1.jar +100: Continue +101: Switching Protocols +102: Processing +103: Early Hints +110: Response is Stale +111: Revalidation Failed +112: Disconnected Operation +113: Heuristic Expiration +199: Miscellaneous Warning +200: OK +201: Created +202: Accepted +203: Non-Authoritative Information +... +``` + +You can also print status codes by [response classes](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes): + +```console +$ java -jar httpstatus-1.1.1.jar 2xx +200: OK +201: Created +202: Accepted +203: Non-Authoritative Information +... +``` + +## Contributing + +If you want to contribute to this project, all you have to do is clone the GitHub +repository: + +```console +git clone git@github.com:ethauvin/HttpStatus.git +``` + +Then use [bld](https://rife2.com/bld) to build: + +```console +cd HttpStatus +./bld compile +``` + +The project has an [IntelliJ IDEA](https://www.jetbrains.com/idea/) project structure. You can just open it after all +the dependencies were downloaded and peruse the code. diff --git a/bld b/bld new file mode 100755 index 0000000..0880033 --- /dev/null +++ b/bld @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +java -jar "$(dirname "$0")/lib/bld/bld-wrapper.jar" "$0" --build net.thauvin.erik.httpstatus.HttpStatusBuild "$@" \ No newline at end of file diff --git a/bld.bat b/bld.bat new file mode 100644 index 0000000..2810ee8 --- /dev/null +++ b/bld.bat @@ -0,0 +1,4 @@ +@echo off +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +java -jar "%DIRNAME%/lib/bld/bld-wrapper.jar" "%0" --build net.thauvin.erik.httpstatus.HttpStatusBuild %* \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 56c327b..0000000 --- a/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'java' -apply plugin: 'idea' -apply plugin: 'application' - -defaultTasks 'deploy' - -version = '1.0' - -def deployDir = 'deploy' -def buildProp = 'build' -def buildProps = 'buildnumber.properties' -def isRelease = 'release' in gradle.startParameter.taskNames - -mainClassName = 'net.thauvin.erik.httpstatus.Reasons' -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' - -repositories { - mavenCentral() -} - -dependencies { - compile 'servletapi:servlet-api:+' - compile 'javax.servlet.jsp:jsp-api:+' - - testCompile 'org.testng:testng:+' -} - -compileJava { - doFirst { - if (isRelease) - { - ant.propertyfile(file: buildProps) { - entry(key: buildProp, - type: 'int', - default: '-1', - operation: '+') - } - } - } - //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" -} - - -jar { - def props = new Properties() - file(buildProps).withInputStream { stream -> props.load(stream) } - version = version + '.' + props.get(buildProp) - archiveName = archiveName.toLowerCase() - manifest.attributes('Main-Class': mainClassName) -} - -clean { - delete deployDir -} - -test { - useTestNG() -} - -task wrapper(type: Wrapper) { - gradleVersion = gradle.gradleVersion -} - -task copyToDeploy(type: Copy) { - from(configurations.runtime) { - exclude 'servlet-api-*.jar' - exclude 'jsp-api-*.jar' - } - from jar - into deployDir -} - -task deploy(dependsOn: ['build', 'copyToDeploy']) { - description = "Copies all needed files to the ${deployDir} directory." - group = "Publishing" - outputs.dir deployDir - inputs.files copyToDeploy - mustRunAfter clean -} - -task release(dependsOn: ['deploy', 'wrapper']) { - group = "Publishing" - description = "Releases new version." -} \ No newline at end of file diff --git a/buildnumber.properties b/buildnumber.properties deleted file mode 100644 index d2aa0ba..0000000 --- a/buildnumber.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Wed, 02 Dec 2015 15:38:42 -0800 -build=0 diff --git a/config/pmd.xml b/config/pmd.xml new file mode 100644 index 0000000..2641880 --- /dev/null +++ b/config/pmd.xml @@ -0,0 +1,109 @@ + + + Erik's Ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 9411448..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 62ef353..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Dec 02 13:19:10 PST 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip diff --git a/gradlew b/gradlew deleted file mode 100644 index 9d82f78..0000000 --- a/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 8a0b282..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lib/bld/bld-wrapper.jar b/lib/bld/bld-wrapper.jar new file mode 100644 index 0000000..7d0825c Binary files /dev/null and b/lib/bld/bld-wrapper.jar differ diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties new file mode 100644 index 0000000..418f275 --- /dev/null +++ b/lib/bld/bld-wrapper.properties @@ -0,0 +1,7 @@ +bld.downloadExtensionJavadoc=false +bld.downloadExtensionSources=true +bld.downloadLocation= +bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.10 +bld.extension-pmd=com.uwyn.rife2:bld-pmd:1.2.3 +bld.repositories=MAVEN_CENTRAL,RIFE2_RELEASES,MAVEN_LOCAL,RIFE2_SNAPSHOTS +bld.version=2.2.1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..73cf087 --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + net.thauvin.erik.httpstatus + httpstatus + 1.1.2-SNAPSHOT + HttpStatus + Tag library to display the code, reason, cause and/or message for HTTP status codes in JSP error pages + https://github.com/ethauvin/HttpStatus + + + The BSD 3-Clause License + https://opensource.org/licenses/BSD-3-Clause + + + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + compile + + + jakarta.servlet.jsp + jakarta.servlet.jsp-api + 4.0.0 + compile + + + jakarta.el + jakarta.el-api + 6.0.1 + compile + + + + + ethauvin + Erik C. Thauvin + erik@thauvin.net + https://erik.thauvin.net/ + + + + scm:git:https://github.com/ethauvin/HttpStatus.git + scm:git:git@github.com:ethauvin/HttpStatus.git + https://github.com/ethauvin/HttpStatus + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 1fbf6b6..0000000 --- a/settings.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This settings file was auto generated by the Gradle buildInit task - * by 'erik' at '12/2/15 1:19 PM' with Gradle 2.9 - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at https://docs.gradle.org/2.9/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - -rootProject.name = 'HttpStatus' diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..468f000 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.organization=ethauvin-github +sonar.projectKey=ethauvin_HttpStatus +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml +sonar.sources=src/main/java/ +sonar.tests=src/test/java/ +sonar.java.binaries=build/main,build/test +sonar.java.libraries=lib/compile/*.jar \ No newline at end of file diff --git a/src/bld/java/net/thauvin/erik/httpstatus/HttpStatusBuild.java b/src/bld/java/net/thauvin/erik/httpstatus/HttpStatusBuild.java new file mode 100644 index 0000000..9c36ca8 --- /dev/null +++ b/src/bld/java/net/thauvin/erik/httpstatus/HttpStatusBuild.java @@ -0,0 +1,157 @@ +/* + * HttpStatusBuild.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus; + +import rife.bld.BuildCommand; +import rife.bld.Project; +import rife.bld.extension.JacocoReportOperation; +import rife.bld.extension.PmdOperation; +import rife.bld.publish.*; +import rife.tools.exceptions.FileUtilsErrorException; + +import java.io.File; +import java.util.List; +import java.util.jar.Attributes; + +import static rife.bld.dependencies.Repository.*; +import static rife.bld.dependencies.Scope.compile; +import static rife.bld.dependencies.Scope.test; +import static rife.bld.operations.JavadocOptions.DocLinkOption.NO_MISSING; + +public class HttpStatusBuild extends Project { + final PmdOperation pmdOp = new PmdOperation() + .fromProject(this) + .failOnViolation(true) + .ruleSets("config/pmd.xml"); + + public HttpStatusBuild() { + pkg = "net.thauvin.erik.httpstatus"; + name = "HttpStatus"; + version = version(1, 1, 2, "SNAPSHOT"); + + var description = "Tag library to display the code, reason, cause and/or message for HTTP status codes in JSP error pages"; + var url = "https://github.com/ethauvin/HttpStatus"; + + mainClass = "net.thauvin.erik.httpstatus.Reasons"; + + javaRelease = 17; + + downloadSources = true; + autoDownloadPurge = true; + repositories = List.of(MAVEN_CENTRAL, SONATYPE_SNAPSHOTS); + + scope(compile) + .include(dependency("jakarta.servlet", "jakarta.servlet-api", version(6, 1, 0))) + .include(dependency("jakarta.servlet.jsp", "jakarta.servlet.jsp-api", version(4, 0, 0))) + .include(dependency("jakarta.el", "jakarta.el-api", version(6, 0, 1))); + scope(test) + .include(dependency("org.assertj", "assertj-core", version(3, 27, 3))) + .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 2))) + .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 2))); + + jarOperation().manifestAttribute(Attributes.Name.MAIN_CLASS, pkg + '.' + "Reasons"); + + javadocOperation().javadocOptions() + .docTitle(description + ' ' + version.toString()) + .docLint(NO_MISSING) + .link("https://jakarta.ee/specifications/platform/9/apidocs/"); + + publishOperation() + .repository(version.isSnapshot() ? repository(SONATYPE_SNAPSHOTS_LEGACY.location()) + .withCredentials(property("sonatype.user"), property("sonatype.password")) + : repository(SONATYPE_RELEASES_LEGACY.location()) + .withCredentials(property("sonatype.user"), property("sonatype.password"))) + .repository(repository("github")) + .info(new PublishInfo() + .groupId(pkg) + .artifactId(name.toLowerCase()) + .name(name) + .version(version) + .description(description) + .url(url) + .developer(new PublishDeveloper() + .id("ethauvin") + .name("Erik C. Thauvin") + .email("erik@thauvin.net") + .url("https://erik.thauvin.net/") + ) + .license(new PublishLicense() + .name("The BSD 3-Clause License") + .url("https://opensource.org/licenses/BSD-3-Clause") + ) + .scm(new PublishScm() + .connection("scm:git:" + url + ".git") + .developerConnection("scm:git:git@github.com:ethauvin/" + name + ".git") + .url(url)) + .signKey(property("sign.key")) + .signPassphrase(property("sign.passphrase"))); + } + + public static void main(String[] args) { + new HttpStatusBuild().start(args); + } + + @BuildCommand(summary = "Generates JaCoCo Reports") + public void jacoco() throws Exception { + new JacocoReportOperation() + .fromProject(this) + .execute(); + } + + @BuildCommand(summary = "Runs PMD analysis") + public void pmd() throws Exception { + pmdOp.execute(); + } + + @BuildCommand(value = "pmd-cli", summary = "Runs PMD analysis (CLI)") + public void pmdCli() throws Exception { + pmdOp.includeLineNumber(false).execute(); + } + + private void pomRoot() throws FileUtilsErrorException { + PomBuilder.generateInto(publishOperation().fromProject(this).info(), dependencies(), + new File(workDirectory, "pom.xml")); + } + + @Override + public void publish() throws Exception { + super.publish(); + pomRoot(); + } + + @Override + public void publishLocal() throws Exception { + super.publishLocal(); + pomRoot(); + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/Reasons.java b/src/main/java/net/thauvin/erik/httpstatus/Reasons.java index 1ef88fc..62b20f0 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/Reasons.java +++ b/src/main/java/net/thauvin/erik/httpstatus/Reasons.java @@ -1,102 +1,122 @@ /* * Reasons.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus; import java.util.Map; import java.util.ResourceBundle; -import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; /** - * The Reasons class. + * Populates the {@link #REASON_PHRASES reason phrases} map from {@link #BUNDLE_BASENAME bundle properties}, and + * implements accessor methods. * * @author Erik C. Thauvin * @created 2015-12-02 * @since 1.0 */ -public class Reasons -{ - /** - * The reason phrases map. - */ - private static final Map REASON_PHRASES = new TreeMap(); +public final class Reasons { + /** + * The resource bundle base name. + */ + static final String BUNDLE_BASENAME = "net.thauvin.erik.httpstatus.reasons"; + /** + * The reason phrases map. + */ + private static final Map REASON_PHRASES = new ConcurrentHashMap<>(); - /** - * Gets the reason phrase for the specified status code. - * - * @param statusCode The status code. - * - * @return The reason phrase, or null. - */ - public static String getReasonPhrase(int statusCode) - { - return getReasonPhrase(Integer.toString(statusCode)); - } + // Initializes the reason phrases map. + static { + var bundle = ResourceBundle.getBundle(BUNDLE_BASENAME); + for (var key : bundle.keySet()) { + REASON_PHRASES.put(key, bundle.getString(key)); + } + } - /** - * Gets the reason phrase for the specified status code. - * - * @param statusCode The status code. - * - * @return The reason phrase, or null. - */ - public static String getReasonPhrase(String statusCode) - { - return REASON_PHRASES.get(statusCode); - } + /** + * Disables the default constructor. + */ + private Reasons() { + throw new UnsupportedOperationException("Illegal constructor call."); + } - /** - * Outputs the status codes and reason phrases. - * - * @param args The command line arguments. - */ - public static void main(String[] args) - { - for (final Map.Entry entry : REASON_PHRASES.entrySet()) - { - System.out.println(entry.getKey() + ": " + entry.getValue()); - } - } + /** + * Returns the reason phrase for the specified status code. + * + * @param statusCode The status code. + * @return The reason phrase, or null. + */ + public static String getReasonPhrase(int statusCode) { + return getReasonPhrase(Integer.toString(statusCode)); + } - /** - * Initializes the reason phrases map. - */ - static - { - final ResourceBundle bundle = ResourceBundle.getBundle("net.thauvin.erik.httpstatus.reasons"); - for (final String key : bundle.keySet()) - { - REASON_PHRASES.put(key, bundle.getString(key)); - } - } -} \ No newline at end of file + /** + * Returns the reason phrase for the specified status code. + * + * @param statusCode The status code. + * @return The reason phrase, or null. + */ + public static String getReasonPhrase(String statusCode) { + return REASON_PHRASES.get(statusCode); + } + + /** + * Prints the reason phrase for the given status code(s). + * + * @param args The status code(s) or response class(es), prints all if none. + */ + @SuppressWarnings("PMD.SystemPrintln") + public static void main(String... args) { + var keys = new TreeSet<>(REASON_PHRASES.keySet()); + if (args.length >= 1) { + for (var arg : args) { + if (arg.endsWith("xx")) { // e.g.: 2xx + var responseClass = arg.charAt(0); + keys.forEach(k -> { + if (k.charAt(0) == responseClass) { + System.out.println(k + ": " + REASON_PHRASES.get(k)); + } + }); + } else { // e.g.: 404 + var value = REASON_PHRASES.get(arg); + if (value != null) { + System.out.println(arg + ": " + value); + } + } + } + } else { // Print all + keys.forEach(k -> System.out.println(k + ": " + REASON_PHRASES.get(k))); + System.out.println("Total: " + REASON_PHRASES.size()); + } + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/StatusCode.java b/src/main/java/net/thauvin/erik/httpstatus/StatusCode.java new file mode 100644 index 0000000..bbcb2ea --- /dev/null +++ b/src/main/java/net/thauvin/erik/httpstatus/StatusCode.java @@ -0,0 +1,153 @@ +/* + * StatusCode.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus; + +import java.io.Serial; +import java.io.Serializable; + +/** + * The StatusCode bean implements methods to check the class of an HTTP status code. + * + * @author Erik C. Thauvin + * @since 1.1.0 + */ +public class StatusCode implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + private int code; + + /** + * Creates a new StatusCode object. + */ + public StatusCode() { + // Default constructor. + } + + /** + * Creates a new StatusCode object. + * + * @param code The status code. + */ + public StatusCode(int code) { + this.code = code; + } + + /** + * Returns the status code. + */ + public int getCode() { + return code; + } + + /** + * Returns the reason for the status code. + * + * @return The reason, or null. + * @see Reasons#getReasonPhrase(int) + */ + public String getReason() { + return Reasons.getReasonPhrase(code); + } + + /** + * Checks if the status code is a client error. (eg: Internal Server Error) + * + * @return true if the status code is a client error, false otherwise. + */ + public boolean isClientError() { + return code >= 400 && code < 500; + } + + /** + * Checks if the status code is a client or server error. + * + * @return true if the status code is an error, false otherwise. + */ + public boolean isError() { + return code >= 400 && code < 600; + } + + /** + * Checks if the status code is informational. + * + * @return true if the status code is informational, false otherwise. + */ + public boolean isInfo() { + return code >= 100 && code < 200; + } + + /** + * Checks if the status code is a redirect. + * + * @return true if the status code is a redirect, false otherwise. + */ + public boolean isRedirect() { + return code >= 300 && code < 400; + } + + /** + * Checks if the status code is a server error. + * + * @return true if the status code is a server error, false otherwise. + */ + public boolean isServerError() { + return code >= 500 && code < 600; + } + + /** + * Checks if the status code is a (OK) success. + * + * @return true if the status code is a success, false otherwise. + */ + public boolean isSuccess() { + return code >= 200 && code < 300; + } + + /** + * Checks if the status code is valid. + * + * @return true if the status code is valid, false otherwise. + */ + public boolean isValid() { + return code == 783 || (code >= 100 && code < 600); + } + + /** + * Sets the status code. + * + * @param code The HTTP status code. + */ + public void setCode(int code) { + this.code = code; + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/Utils.java b/src/main/java/net/thauvin/erik/httpstatus/Utils.java index 910e2c9..5d42b22 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/Utils.java +++ b/src/main/java/net/thauvin/erik/httpstatus/Utils.java @@ -1,134 +1,97 @@ /* * Utils.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus; import java.io.IOException; import java.io.Writer; /** - * The Utils class. + * The Utils class implements a collection of utility methods used throughout this project. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -public class Utils -{ +public final class Utils { + /** + * Disables the default constructor. + * + * @throws UnsupportedOperationException If the constructor is called. + */ + private Utils() { + throw new UnsupportedOperationException("Illegal constructor call."); + } - /** - * Disables the default constructor. - * - * @throws UnsupportedOperationException if an error occurred. if the constructor is called. - */ - private Utils() - throws UnsupportedOperationException - { - throw new UnsupportedOperationException("Illegal constructor call."); - } + /** + * Converts <, >, &, ', " + * to their corresponding entity codes. + * + * @param value The string value to convert. + * @return The converted string value. + */ + public static String escapeXml(String value) { + var escaped = new StringBuilder(); - /** - * Writes a string value. - * - * @param out The writer to output the value to. - * @param value The string value. - * @param defaultValue The default value. - * @param xml The xml flag. - */ - public static void outWrite(Writer out, String value, String defaultValue, boolean xml) - throws IOException - { - if (xml) - { - if (value != null) - { - out.write(escapeXml(value)); - } - else if (defaultValue != null) - { - out.write(escapeXml(defaultValue)); - } - } - else - { - if (value != null) - { - out.write(value); - } - else if (defaultValue != null) - { - out.write(defaultValue); - } - } - } + for (var i = 0; i < value.length(); i++) { + var c = value.charAt(i); + switch (c) { + case '<' -> escaped.append("<"); + case '>' -> escaped.append(">"); + case '&' -> escaped.append("&"); + case '\'' -> escaped.append("'"); + case '"' -> escaped.append("""); + default -> escaped.append(c); + } + } - /** - * Escapes a string value. - * - * @param value The string value to escape. - * - * @return The escaped string value. - */ - public static String escapeXml(String value) - { - final StringBuilder escaped = new StringBuilder(); + return escaped.toString(); + } - for (int i = 0; i < value.length(); i++) - { - final char c = value.charAt(i); - switch (c) - { - case '<': - escaped.append("<"); - break; - case '>': - escaped.append(">"); - break; - case '&': - escaped.append("&"); - break; - case '\'': - escaped.append("'"); - break; - case '"': - escaped.append("""); - break; - default: - escaped.append(c); - break; - } - } - - return escaped.toString(); - } -} \ No newline at end of file + /** + * Writes a string value to the specified writer. The default value is used when the actual value is null. + * + * @param out The writer to output the value to. + * @param value The string value. + * @param defaultValue The default value. + * @param xml The {@link #escapeXml(String) xml} flag. + * @throws IOException If an I/O error occurs. + */ + public static void outWrite(Writer out, String value, String defaultValue, boolean xml) + throws IOException { + if (value != null) { + out.write(xml ? escapeXml(value) : value); + } else if (defaultValue != null) { + out.write(xml ? escapeXml(defaultValue) : defaultValue); + } + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/package.html b/src/main/java/net/thauvin/erik/httpstatus/package.html new file mode 100644 index 0000000..07e4484 --- /dev/null +++ b/src/main/java/net/thauvin/erik/httpstatus/package.html @@ -0,0 +1,11 @@ + + + + HttpStatus JSP Tag Library + + +A simple JSP Tag Library to display the code, reason and/or cause for HTTP status codes in JSP error pages. + +@since 1.0 + + diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/CauseTag.java b/src/main/java/net/thauvin/erik/httpstatus/taglibs/CauseTag.java index d9f9be6..2bf4018 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/taglibs/CauseTag.java +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/CauseTag.java @@ -1,72 +1,78 @@ /* * CauseTag.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus.taglibs; +import jakarta.servlet.jsp.JspWriter; +import jakarta.servlet.jsp.PageContext; import net.thauvin.erik.httpstatus.Utils; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.PageContext; import java.io.IOException; + /** - * The CauseTag class. + * The <hs:cause> tag returns the cause (if any) for the current HTTP Status Error Code. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -public class CauseTag extends XmlSupport -{ - @Override - public void doTag() - throws JspException, IOException - { - final PageContext pageContext = (PageContext) getJspContext(); - final JspWriter out = pageContext.getOut(); +public class CauseTag extends XmlSupport { + /** + * Prints the cause (if any) for the current HTTP Status Error Code. + * + * @throws IOException If an error occurs while writing the output. + */ + @Override + public void doTag() throws IOException { + PageContext pageContext = (PageContext) getJspContext(); + JspWriter out = pageContext.getOut(); - String cause; + Throwable cause = pageContext.getErrorData().getThrowable().getCause(); - try - { - cause = pageContext.getErrorData().getThrowable().getCause().getLocalizedMessage(); - } - catch (NullPointerException ignore) - { - cause = defaultValue; - } + Utils.outWrite(out, getCause(cause), defaultValue, escapeXml); + } - Utils.outWrite(out, cause, defaultValue, escapeXml); - } -} \ No newline at end of file + /** + * Returns the cause's localized message or default value. + * + * @param cause The cause. + * @return The cause or {@code null}. + */ + public String getCause(Throwable cause) { + if (cause != null && cause.getLocalizedMessage() != null) { + return cause.getLocalizedMessage(); + } else { + return null; + } + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/CodeTag.java b/src/main/java/net/thauvin/erik/httpstatus/taglibs/CodeTag.java index c2f1517..f74b016 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/taglibs/CodeTag.java +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/CodeTag.java @@ -1,60 +1,61 @@ /* * CodeTag.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus.taglibs; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.PageContext; -import javax.servlet.jsp.tagext.SimpleTagSupport; +import jakarta.servlet.jsp.JspWriter; +import jakarta.servlet.jsp.PageContext; +import jakarta.servlet.jsp.tagext.SimpleTagSupport; + import java.io.IOException; /** - * The CodeTag class. + * The <hs:code> tag returns the HTTP Status Error Code. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -public class CodeTag extends SimpleTagSupport -{ - @Override - public void doTag() - throws JspException, IOException - { - final PageContext pageContext = (PageContext) getJspContext(); - final JspWriter out = pageContext.getOut(); +public class CodeTag extends SimpleTagSupport { + /** + * Writes the HTTP Status Error Code to the current JspWriter. + * + * @throws IOException If an I/O error occurs. + */ + @Override + public void doTag() throws IOException { + PageContext pageContext = (PageContext) getJspContext(); + JspWriter out = pageContext.getOut(); - out.write(String.valueOf(pageContext.getErrorData().getStatusCode())); - } + out.write(String.valueOf(pageContext.getErrorData().getStatusCode())); + } } \ No newline at end of file diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/MessageTag.java b/src/main/java/net/thauvin/erik/httpstatus/taglibs/MessageTag.java new file mode 100644 index 0000000..7674774 --- /dev/null +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/MessageTag.java @@ -0,0 +1,64 @@ +/* + * MessageTag.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus.taglibs; + +import jakarta.servlet.jsp.JspWriter; +import jakarta.servlet.jsp.PageContext; +import net.thauvin.erik.httpstatus.Utils; + +import java.io.IOException; + +/** + * The <hs:message> tag returns the message (if any) for the current error. + * + * @author Erik C. Thauvin + * @created 2022-03-16 + * @since 1.0.5 + */ +public class MessageTag extends XmlSupport { + /** + * Writes the error message associated with the current HTTP Status Error Code. + * + * @throws IOException If an I/O error occurs. + */ + @Override + public void doTag() throws IOException { + PageContext pageContext = (PageContext) getJspContext(); + JspWriter out = pageContext.getOut(); + + String message = (String) pageContext.getRequest().getAttribute( + jakarta.servlet.RequestDispatcher.ERROR_MESSAGE); + + Utils.outWrite(out, message, defaultValue, escapeXml); + } +} diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/ReasonTag.java b/src/main/java/net/thauvin/erik/httpstatus/taglibs/ReasonTag.java index b77f2a2..16cd818 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/taglibs/ReasonTag.java +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/ReasonTag.java @@ -1,94 +1,81 @@ /* * ReasonTag.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus.taglibs; +import jakarta.servlet.jsp.JspWriter; +import jakarta.servlet.jsp.PageContext; import net.thauvin.erik.httpstatus.Reasons; import net.thauvin.erik.httpstatus.Utils; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.PageContext; import java.io.IOException; /** - * The ReasonTag class. + * The <hs:reason> tag returns the Reason Phrase for the current (or specified) HTTP Status Error + * Code. * * @author Erik C. Thauvin * @created 2015-12-02 * @since 1.0 */ -public class ReasonTag extends XmlSupport -{ - private int statusCode; +public class ReasonTag extends XmlSupport { + private int statusCode = -1; - @Override - public void doTag() - throws JspException, IOException - { - final PageContext pageContext = (PageContext) getJspContext(); - final JspWriter out = pageContext.getOut(); - - try - { - if (statusCode > 0) - { - Utils.outWrite(out, Reasons.getReasonPhrase(statusCode), defaultValue, escapeXml); - } - else - { - Utils.outWrite(out, - Reasons.getReasonPhrase(pageContext.getErrorData().getStatusCode()), - defaultValue, - escapeXml); - } - } - catch (IOException ignore) - { - // Ignore. - } - } - - /** - * Sets the status code. - * - * @param statusCode The status code. - */ - @SuppressWarnings("unused") - public void setCode(int statusCode) - { - this.statusCode = statusCode; - } + /** + * Writes the Reason Phrase for the current (or specified) HTTP Status Error Code. + */ + @Override + public void doTag() { + PageContext pageContext = (PageContext) getJspContext(); + JspWriter out = pageContext.getOut(); + try { + if (statusCode > -1) { + Utils.outWrite(out, Reasons.getReasonPhrase(statusCode), defaultValue, escapeXml); + } else { + Utils.outWrite(out, Reasons.getReasonPhrase(pageContext.getErrorData().getStatusCode()), defaultValue, + escapeXml); + } + } catch (IOException ignored) { + // Ignore. + } + } + /** + * Sets the status code. + * + * @param statusCode The status code. + */ + public void setCode(int statusCode) { + this.statusCode = statusCode; + } } diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/XmlSupport.java b/src/main/java/net/thauvin/erik/httpstatus/taglibs/XmlSupport.java index d466137..5898da1 100644 --- a/src/main/java/net/thauvin/erik/httpstatus/taglibs/XmlSupport.java +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/XmlSupport.java @@ -1,72 +1,74 @@ /* * XmlSupport.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus.taglibs; -import javax.servlet.jsp.tagext.SimpleTagSupport; +import jakarta.servlet.jsp.tagext.SimpleTagSupport; /** - * The XmlSupport class. + * Adds support for the default and escapeXml tag attributes. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -public abstract class XmlSupport extends SimpleTagSupport -{ - protected String defaultValue; +public abstract class XmlSupport extends SimpleTagSupport { + /** + * Default value string. + */ + String defaultValue; - protected boolean escapeXml = true; + /** + * Escape XML flag. + */ + boolean escapeXml = true; - /** - * Sets the default value. - * - * @param defaultValue The default value. - */ - @SuppressWarnings("unused") - public void setDefault(String defaultValue) - { - this.defaultValue = defaultValue; - } + /** + * Sets the default value. + * + * @param defaultValue The default value. + */ + @SuppressWarnings("unused") + public void setDefault(String defaultValue) { + this.defaultValue = defaultValue; + } - /** - * Sets the escapeXMl flag. - * - * @param escapeXml true or false - */ - @SuppressWarnings("unused") - public void setEscapeXml(boolean escapeXml) - { - this.escapeXml = escapeXml; - } + /** + * Sets the {@link net.thauvin.erik.httpstatus.Utils#escapeXml(String) xml} flag. + * + * @param escapeXml true or false + */ + @SuppressWarnings("unused") + public void setEscapeXml(boolean escapeXml) { + this.escapeXml = escapeXml; + } } \ No newline at end of file diff --git a/src/main/java/net/thauvin/erik/httpstatus/taglibs/package.html b/src/main/java/net/thauvin/erik/httpstatus/taglibs/package.html new file mode 100644 index 0000000..712a542 --- /dev/null +++ b/src/main/java/net/thauvin/erik/httpstatus/taglibs/package.html @@ -0,0 +1,11 @@ + + + + HttpStatus JSP Tag Library + + +This package contains the JSP tags used to display the code, reason and/or cause for HTTP status codes. + +@since 1.0 + + \ No newline at end of file diff --git a/src/main/resources/META-INF/httpstatus.tld b/src/main/resources/META-INF/httpstatus.tld index 2c42b1f..eb1a1d5 100644 --- a/src/main/resources/META-INF/httpstatus.tld +++ b/src/main/resources/META-INF/httpstatus.tld @@ -1,45 +1,43 @@ - HttpStatus JSP Tag Library HttpStatus JSP Tags - 1.0 + 1.1.1 hs http://erik.thauvin.net/taglibs/httpstatus @@ -60,7 +58,7 @@ - Converts <,>,&,'," to their corresponding entity codes. Value is true by default. + Converts <, > ,& ,' ," to their corresponding entity codes. Value is true by default. escapeXml false @@ -70,13 +68,38 @@ - Outputs the HTTP Status Error Code. + Returns the HTTP Status Error Code. code net.thauvin.erik.httpstatus.taglibs.CodeTag empty + + + Returns the message (if any) for the current error. + + message + net.thauvin.erik.httpstatus.taglibs.MessageTag + empty + + + Default value if the resulting error message is null. + + default + false + true + + + + Converts <, > ,& ,' ," to their corresponding entity codes. Value is true by default. + + escapeXml + false + true + + + Returns the Reason Phrase for the current (or specified) HTTP Status Error Code. @@ -102,12 +125,11 @@ - Converts <,>,&,'," to their corresponding entity codes. Value is true by default. + Converts <, >, &, ', " to their corresponding entity codes. Value is true by default. escapeXml false true - diff --git a/src/main/resources/net/thauvin/erik/httpstatus/reasons.properties b/src/main/resources/net/thauvin/erik/httpstatus/reasons.properties index 8f99dac..7cb399f 100644 --- a/src/main/resources/net/thauvin/erik/httpstatus/reasons.properties +++ b/src/main/resources/net/thauvin/erik/httpstatus/reasons.properties @@ -1,6 +1,44 @@ +# +# reasons.properties +# +# Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of this project nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + 100=Continue 101=Switching Protocols 102=Processing +103=Early Hints +110=Response is Stale +111=Revalidation Failed +112=Disconnected Operation +113=Heuristic Expiration +199=Miscellaneous Warning 200=OK 201=Created 202=Accepted @@ -10,14 +48,17 @@ 206=Partial Content 207=Multi-Status 208=Already Reported +214=Transformation Applied +218=This is fine 226=IM Used +299=Miscellaneous Persistent Warning 300=Multiple Choices 301=Moved Permanently -302=Moved Temporarily +302=Found/Moved Temporarily 303=See Other 304=Not Modified 305=Use Proxy -306=Switch Proxy +306=Unused 307=Temporary Redirect 308=Permanent Redirect 400=Bad Request @@ -33,31 +74,35 @@ 410=Gone 411=Length Required 412=Precondition Failed -413=Request Entity Too Large -414=Request-URI Too Long +413=Payload Too Large +414=URI Too Long 415=Unsupported Media Type -416=Requested Range Not Satisfiable +416=Range Not Satisfiable 417=Expectation Failed 418=I'm A Teapot 419=Insufficient Space on Resource 420=Method Failure 421=Misdirected Request -422=Unprocessable Entity +422=Unprocessable Content 423=Locked 424=Failed Dependency +425=Too Early 426=Upgrade Required 428=Precondition Required 429=Too Many Requests +430=Request Header Fields Too Large 431=Request Header Fields Too Large 440=Login Timeout 444=No Response 449=Retry With 450=Blocked by Windows Parental Controls 451=Unavailable For Legal Reasons +460=Client Closed Connection Before Load Balancer Idle Timeout +463=X-Forwarded-For Header with More than 30 IP Addresses 494=Request Header Too Large -495=Cert Error -496=No Cert -497=HTTP to HTTPS +495=SSL Certificate Error +496=SSL Certificate Required +497=HTTP Request Sent to HTTPS Port 498=Token Expired/Invalid 499=Client Closed Request 500=Internal Server Error @@ -73,6 +118,17 @@ 510=Not Extended 511=Network Authentication Required 520=Unknown Error -522=Origin Connection Time-out +521=Web Server Is Down +522=Connection Timed Out +523=Origin Is Unreachable +524=A Timeout Occurred +525=SSL Handshake Failed +526=Invalid SSL Certificate +527=Railgun Error +529=Site is overloaded +530=Site is frozen +540=Temporarily Disabled +561=Unauthorized 598=Network Read Timeout Error -599=Network Connect Timeout Error \ No newline at end of file +599=Network Connect Timeout Error +783=Unexpected Token diff --git a/src/test/java/net/thauvin/erik/httpstatus/CauseTagTest.java b/src/test/java/net/thauvin/erik/httpstatus/CauseTagTest.java new file mode 100644 index 0000000..82d9705 --- /dev/null +++ b/src/test/java/net/thauvin/erik/httpstatus/CauseTagTest.java @@ -0,0 +1,58 @@ +/* + * CauseTagTest.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus; + +import net.thauvin.erik.httpstatus.taglibs.CauseTag; +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.junit.jupiter.api.Test; + +/** + * Implements the CauseTagTest class. + * + * @author Erik C. Thauvin + * @since 1.1.0 + */ +class CauseTagTest { + @Test + void causeTest() { + var message = "This is the cause"; + var tag = new CauseTag(); + + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(tag.getCause(new Exception(message))).as("has cause").isEqualTo(message); + softly.assertThat(tag.getCause(new Exception())).as("no cause").isNull(); + softly.assertThat(tag.getCause(null)).as("null").isNull(); + softly.assertThat(tag.getCause(new Exception(""))).as("empty").isEmpty(); + } + } +} diff --git a/src/test/java/net/thauvin/erik/httpstatus/ReasonsMainTest.java b/src/test/java/net/thauvin/erik/httpstatus/ReasonsMainTest.java new file mode 100644 index 0000000..f4c8278 --- /dev/null +++ b/src/test/java/net/thauvin/erik/httpstatus/ReasonsMainTest.java @@ -0,0 +1,104 @@ +/* + * ReasonsMainTest.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Main Class Tests. + * + * @author Erik C. Thauvin + * @created 2019-05-06 + * @since 1.0 + */ +class ReasonsMainTest { + private static final ByteArrayOutputStream OUTPUT_STREAM = new ByteArrayOutputStream(); + private static final PrintStream SYSTEM_OUT = System.out; + + @AfterAll + public static void restoreStreams() { + System.setOut(SYSTEM_OUT); + } + + @BeforeAll + public static void setUpStreams() { + System.setOut(new PrintStream(OUTPUT_STREAM)); + } + + @BeforeEach + public void resetStreams() { + OUTPUT_STREAM.reset(); + } + + @Test + void testMain() { + Reasons.main("401"); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase("401")).as("401"); + assertThat(OUTPUT_STREAM.toString()).doesNotContain("500").as("401 no 500"); + } + + @Test + void testMainAll() { + Reasons.main(); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase(301)).as("301"); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase(404)).as("404"); + } + + @Test + void testMainArgs() { + Reasons.main("500", "302"); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase("500")).as("500 (302)"); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase("302")).as("(500) 302"); + assertThat(OUTPUT_STREAM.toString()).doesNotContain("404").as("500/302 not 404"); + } + + @Test + void testMainArgsClass() { + Reasons.main("2xx"); + assertThat(OUTPUT_STREAM.toString()).contains(Reasons.getReasonPhrase("200")).as("2xx"); + } + + @Test + void testMainInvalid() { + Reasons.main("aaa"); + assertThat(OUTPUT_STREAM.toString()).as("invalid argument: aaa").isEmpty(); + } +} diff --git a/src/test/java/net/thauvin/erik/httpstatus/ReasonsTest.java b/src/test/java/net/thauvin/erik/httpstatus/ReasonsTest.java index fa05d71..406d01e 100644 --- a/src/test/java/net/thauvin/erik/httpstatus/ReasonsTest.java +++ b/src/test/java/net/thauvin/erik/httpstatus/ReasonsTest.java @@ -1,73 +1,59 @@ /* * ReasonsTest.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ResourceBundle; +import static org.assertj.core.api.Assertions.assertThat; + /** - * The ReasonsTest class. + * Reasons Tests. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -@SuppressWarnings("unused") -public class ReasonsTest -{ - @DataProvider(name = "reasons") - public Object[][] reasons() - { - final ResourceBundle bundle = ResourceBundle.getBundle("net.thauvin.erik.httpstatus.reasons"); - final Object[][] reasons = new String[bundle.keySet().size()][2]; - int i = 0; - for (final String key : bundle.keySet()) - { - reasons[i][0] = key; - reasons[i][1] = bundle.getString(key); - i++; - } - return reasons; - } +class ReasonsTest { + @Test + void testGetReasonPhrase() { + var bundle = ResourceBundle.getBundle(Reasons.BUNDLE_BASENAME); + for (var key : bundle.keySet()) { + assertThat(Reasons.getReasonPhrase(key)).as("getReasonPhrase(%s)", key).isEqualTo(bundle.getString(key)); + assertThat(Reasons.getReasonPhrase(Integer.parseInt(key))).as("getReasonPhrase(%s)", key) + .isEqualTo(bundle.getString(key)); + } - @Test(dataProvider = "reasons") - public void testGetReasonPhrase(String code, String reason) - throws Exception - { - Assert.assertEquals(reason, Reasons.getReasonPhrase(code)); - } -} \ No newline at end of file + } +} diff --git a/src/test/java/net/thauvin/erik/httpstatus/StatusCodeTest.java b/src/test/java/net/thauvin/erik/httpstatus/StatusCodeTest.java new file mode 100644 index 0000000..8b1ce4f --- /dev/null +++ b/src/test/java/net/thauvin/erik/httpstatus/StatusCodeTest.java @@ -0,0 +1,96 @@ +/* + * StatusCodeTest.java + * + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.httpstatus; + +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.junit.jupiter.api.Test; + +import java.util.ResourceBundle; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * StatusCode Tests. + * + * @author Erik C. Thauvin + * @since 1.1.0 + */ +class StatusCodeTest { + @Test + void testStatusCode() { + var bundle = ResourceBundle.getBundle(Reasons.BUNDLE_BASENAME); + var statusCode = new StatusCode(); + + try (var softly = new AutoCloseableSoftAssertions()) { + for (var key : bundle.keySet()) { + int code = Integer.parseInt(key); + + statusCode.setCode(code); + softly.assertThat(statusCode.getCode()).as("is not %s", code).isEqualTo(code); + softly.assertThat(statusCode.isInfo()).as("%s is info", code).isEqualTo(code >= 100 && code < 200); + softly.assertThat(statusCode.isSuccess()).as("%s is ok", code).isEqualTo(code >= 200 && code < 300); + softly.assertThat(statusCode.isRedirect()).as("%s is redirect", code) + .isEqualTo(code >= 300 && code < 400); + softly.assertThat(statusCode.isClientError()).as("%s is client error", code) + .isEqualTo(code >= 400 && code < 500); + softly.assertThat(statusCode.isServerError()).as("%s is server error", code) + .isEqualTo(code >= 500 && code < 600); + softly.assertThat(statusCode.isError()).as("%s is error", code).isEqualTo(code >= 400 && code < 600); + softly.assertThat(statusCode.isValid()).as("%s is valid", code).isTrue(); + + softly.assertThat(statusCode.getReason()).as("%s reason phrase is not valid", code) + .isEqualTo(Reasons.getReasonPhrase(code)); + } + } + + try (var softly = new AutoCloseableSoftAssertions()) { + int[] unknowns = {0, 99, 600}; + + for (var code : unknowns) { + statusCode.setCode(code); + softly.assertThat(statusCode.getCode()).as("is not %s", code).isEqualTo(code); + softly.assertThat(statusCode.isInfo()).as("%s is info", code).isFalse(); + softly.assertThat(statusCode.isSuccess()).as("%s is ok", code).isFalse(); + softly.assertThat(statusCode.isRedirect()).as("%s is redirect", code).isFalse(); + softly.assertThat(statusCode.isClientError()).as("%s is client error", code).isFalse(); + softly.assertThat(statusCode.isServerError()).as("%s is server error", code).isFalse(); + softly.assertThat(statusCode.isError()).as("%s is error", code).isFalse(); + softly.assertThat(statusCode.isValid()).as("%s is invalid", code).isFalse(); + softly.assertThat(statusCode.getReason()).as("%s reason phrase is not null.", code).isNull(); + } + } + + statusCode = new StatusCode(900); + assertThat(statusCode.getCode()).as("is not %s", statusCode.getCode()).isEqualTo(900); + } +} diff --git a/src/test/java/net/thauvin/erik/httpstatus/UtilsTest.java b/src/test/java/net/thauvin/erik/httpstatus/UtilsTest.java index 735e693..5c23fd0 100644 --- a/src/test/java/net/thauvin/erik/httpstatus/UtilsTest.java +++ b/src/test/java/net/thauvin/erik/httpstatus/UtilsTest.java @@ -1,58 +1,100 @@ /* * UtilsTest.java * - * Copyright (c) 2015, Erik C. Thauvin (erik@thauvin.net) + * Copyright 2015-2025 Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * modification, are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * Neither the name of the author nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.thauvin.erik.httpstatus; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; /** - * The UtilsTest class. + * Utils Tests. * * @author Erik C. Thauvin * @created 2015-12-03 * @since 1.0 */ -@SuppressWarnings("unused") -public class UtilsTest -{ - @Test - public void testEscapeXml() - throws Exception - { - Assert.assertEquals( - "This is a test. We wan't to make sure that everything is <encoded> according the "encoding" parameter & value.", - Utils.escapeXml( - "This is a test. We wan't to make sure that everything is according the \"encoding\" parameter & value.")); - } -} \ No newline at end of file +class UtilsTest { + @Test + void testEscapeXml() { + assertThat(Utils.escapeXml( + "This is a test. We wan't to make sure that everything is according the \"encoding\" " + + "parameter & value.")) + .isEqualTo("This is a test. We wan't to make sure that everything is <encoded> " + + "according the "encoding" parameter & value."); + } + + @Test + void testOutWrite() throws IOException { + try (var sw = new StringWriter(); var softly = new AutoCloseableSoftAssertions()) { + var defaultValue = "default"; + Utils.outWrite(sw, null, defaultValue, false); + softly.assertThat(sw.toString()).as("outWrite(default)").isEqualTo(defaultValue); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, "", defaultValue, false); + softly.assertThat(sw.toString()).as("outWrite(value empty)").isEmpty(); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, null, null, true); + softly.assertThat(sw.toString()).as("outWrite(null)").isEmpty(); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, "value", defaultValue, false); + softly.assertThat(sw.toString()).as("outWrite(value)").isEqualTo("value"); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, "wan't", defaultValue, true); + softly.assertThat(sw.toString()).as("outWrite(wan't)").isEqualTo("wan't"); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, null, "1 & 1", true); + softly.assertThat(sw.toString()).as("outWrite(1 & 1)").isEqualTo("1 & 1"); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, "", defaultValue, true); + softly.assertThat(sw.toString()).as("outWrite(value empty).as(xml)").isEmpty(); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, null, "", true); + softly.assertThat(sw.toString()).as("outWrite(default empty)").isEmpty(); + + sw.getBuffer().setLength(0); + Utils.outWrite(sw, null, null, true); + softly.assertThat(sw.toString()).as("outWrite(null).as(xml)").isEmpty(); + } + } +}