diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..61d64cb --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +VERSION=6 +PORT=8080 + +jdbc.datasource.default=jdbc/webformulierenverwerker-postgresql \ No newline at end of file diff --git a/.github/workflows/writeBuildInfo.sh b/.github/workflows/writeBuildInfo.sh index 66d46f8..a834640 100644 --- a/.github/workflows/writeBuildInfo.sh +++ b/.github/workflows/writeBuildInfo.sh @@ -6,6 +6,9 @@ if [[ -n $1 ]]; then echo "versionDate_ddmmyyyy=$(date +%d/%m/%Y)" >> classes/BuildInfo.properties echo "configuration.version=$1" > configurations/WebformulierenVerwerker/BuildInfo.properties echo "configuration.timestamp=$(date +%Y%m%d-%H%M%S)" >> configurations/WebformulierenVerwerker/BuildInfo.properties + export instance_version=$1 + export versionDate_yyyymmdd=$(date +%Y-%m-%d) + envsubst < publiccode_template.yaml > publiccode.yaml else echo "writeBuildInfo.sh - no version to write, leaving BuildInfo.properties unchanged" fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index d306423..313b657 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ FrankConfig.xsd # .env should be made in the environment. # It is an additional file to StageSpecifics_LOC to keep it more generic. .env +.idea/ \ No newline at end of file diff --git a/.releaserc b/.releaserc index 0876f0e..e50efd3 100644 --- a/.releaserc +++ b/.releaserc @@ -77,7 +77,9 @@ { "assets": [ "CHANGELOG.md", - "classes/BuildInfo.properties" + "classes/BuildInfo.properties", + "configurations/WebformulierenVerwerker/BuildInfo.properties", + "publiccode.yaml" ], "message": "chore(<%= nextRelease.type %>): release <%= nextRelease.version %> <%= nextRelease.channel !== null ? `on ${nextRelease.channel} channel ` : '' %>[skip ci]\n\n<%= nextRelease.notes %>" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a8888c..76a544e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,136 @@ [![conventional commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow.svg)](https://conventionalcommits.org) [![semantic versioning](https://img.shields.io/badge/semantic%20versioning-2.0.0-green.svg)](https://semver.org) +## [6.5.8](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.7...v6.5.8) (2023-07-05) + + +### 🧑‍💻 Code Refactoring + +* add relationID to CreateMetaDocument ([f163e0b](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/f163e0b3a56ae0841859de282d256674b50cbb9a)) + +## [6.5.7](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.6...v6.5.7) (2023-07-04) + + +### 🐛 Bug Fixes + +* connection pool shut down ([9944019](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/9944019960c893ce6d383d95010e5c5e983bc00e)) + +## [6.5.6](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.5...v6.5.6) (2023-07-04) + + +### 🧑‍💻 Code Refactoring + +* pipe path fix ([42c6fae](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/42c6faee55f276ce9f584be05fa77fee7dcce6c1)) + +## [6.5.4](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.3...v6.5.4) (2023-07-04) + + +### 🐛 Bug Fixes + +* incorrect XSLT element ([a390eae](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/a390eae9fde47b6cb2a49267fd9592357cfc54c2)) + +## [6.5.2](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.1...v6.5.2) (2023-07-04) + + +### 🐛 Bug Fixes + +* enable SOAP validation again ([#54](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/issues/54)) ([17ab996](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/17ab99662af4cbaa487abd690f18bb7eabf949dc)) + +## [6.5.1](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.5.0...v6.5.1) (2023-07-04) + + +### 🧑‍💻 Code Refactoring + +* change object type for company registration ([6418228](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/64182285daac0e90f56ea3d237acb10b777564b8)) +* limit query to 1 result ([63fc149](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/63fc149a693181cb56e883acdb0e59ae35da4496)) +* skip pipes if no afzender ([6ac45c1](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/6ac45c148ee472288d4cc5b9cbf5e88294de47c9)) + +## [6.5.0](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.4.1...v6.5.0) (2023-07-03) + + +### 🍕 Features + +* version publiccode.yaml automatically ([6e4864d](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/6e4864d0d59ec6daefbcebdca0ed850de495a7b3)) + + +### 🐛 Bug Fixes + +* check in publiccode.yaml ([a4c08d9](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/a4c08d961895d7c36ea2bd8fd7c6c8d44832a9d9)) +* CI/CD should check in configurations/WebformulierenVerwerker/BuildInfo.properties ([18da36a](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/18da36aebdeec700a121756b1e9142879b40e4de)) + + +### 📝 Documentation + +* update CONTRIBUTING.md ([11a3694](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/11a36948e0efeebdbed098699908f61abab6d6b4)) + +## [6.4.1](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.4.0...v6.4.1) (2023-07-03) + + +### 🐛 Bug Fixes + +* add missing parameter ([efa742e](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/efa742e7f54e51a0473ff8c57ba237eed5de9ef2)) + +## [6.4.0](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.5...v6.4.0) (2023-07-03) + + +### 🍕 Features + +* add company registration to dispatcher ([d620003](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/d620003fd6f48b73d642284afcfea6dbf0237246)) +* add first iteration opslaanInkNietNatuurlijkPersoon ([84b9861](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/84b98612436ce211f31bf1703816665d40daf1bf)) + + +### 🐛 Bug Fixes + +* incorrect xslt variable ([2a42f41](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/2a42f418daf24e4d973c1cae5d622681c2f1863c)) +* renaming issues ([66ae9db](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/66ae9dbafefebf2722ed0c9b34787ad082bae63a)) + + +### 🧑‍💻 Code Refactoring + +* rename pipe SetONum ([29a2b22](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/29a2b220c2d58478b6837e79dcd14a364bee04e7)) + +## [6.3.5](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.4...v6.3.5) (2023-07-02) + + +### 🐛 Bug Fixes + +* Improve error handling ([#49](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/issues/49)) ([1fd3381](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/1fd338180bab1290a715b972c4159aa1244e2d6b)) +* sync custom code with latest ff ([#50](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/issues/50)) ([948a110](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/948a110bf47a84ff68a9479c7cd45042a195f9f4)) + +## [6.3.4](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.3...v6.3.4) (2023-06-30) + + +### 🐛 Bug Fixes + +* incorrect input for XSLTPipes ([832ed1f](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/832ed1f1a1c772010ef78001109c0081fdc94f78)) + +## [6.3.3](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.2...v6.3.3) (2023-06-30) + + +### 🧑‍💻 Code Refactoring + +* change sessionkey type ([e9fc89b](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/e9fc89ba9985bd7d3460aa7ed9b22e007d901755)) + +## [6.3.2](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.1...v6.3.2) (2023-06-30) + + +### 🧑‍💻 Code Refactoring + +* incorrect table name ([68096ea](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/68096ea7acd188c198cd89ec99b35bfb3f5e1fad)) + +## [6.3.1](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.3.0...v6.3.1) (2023-06-30) + + +### 🧑‍💻 Code Refactoring + +* incorrect names and values ([5c9308e](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/5c9308e3bf27b426f5119d0e9cbe88c43e4803c3)) + +## [6.3.0](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.2.14...v6.3.0) (2023-06-30) + + +### 🍕 Features + +* re-use afzender & onderwerp ([6bb560e](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/commit/6bb560eae45d396c94f876bde76065ef5c0c20a0)) + ## [6.2.14](https://github.com/Sudwest-Fryslan/WebformulierenVerwerker/compare/v6.2.13...v6.2.14) (2023-06-29) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c738d69..534bb2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,12 @@ +# Deliverables + +This project builds the following artifacts: +* A docker image that can be used to run this application stand-alone. +* A .jar file with only the Frank configuration of this project. The .jar file can be uploaded in het-integratie-platform, see https://github.com/wearefrank/het-integratie-platform. + # CI/CD -Releases are created automatically by GitHub Actions, see [.github/workflows/ci-build.yml](.github/workflows/ci-build.yml). +We use conventional commits, see https://www.conventionalcommits.org/en/v1.0.0/. Releases are created automatically by GitHub Actions, see [.github/workflows/ci-build.yml](.github/workflows/ci-build.yml). Please take care to write meaningful commit messages that result in meaningful entries in [CHANGELOG.md](CHANGELOG.md). Here is an example of the commit message for a breaking change: @@ -19,6 +25,7 @@ A breaking change means that this version is not backwards compatible with the p * The commit type (e.g. chore) is still relevant for breaking changes. This information appears in the release notes in the same way as a non-breaking change. * For non-breaking changes, omit the line with BREAKING and make a commit message like the first line shown. +# Checklist for testing CI/CD Here is a checklist for testing the CI/CD. @@ -26,12 +33,12 @@ Here is a checklist for testing the CI/CD. * Do a commit on main that has a commit message starting with `fix:`. The following should happen: * The pipeline succeeds - this checks all authorizations are in place. * A commit with a message starting with `chore:` has been added automatically. - * The extra commit updates files `classes/BuildInfo.properties` and `CHANGELOG.md`. + * The extra commit updates files `classes/BuildInfo.properties`, `configurations/WebformulierenVerwerker/BuildInfo.properties`, `publiccode.yaml` and `CHANGELOG.md`. * These files should have trustworthy contents - speaks for itself. * On GitHub, there is a tag for the new version that starts with `v`. For example if the new release is `3.2.1` then the tag should be `v3.2.1`. You can get this tag using `git fetch origin` on the command line. * The docker image for the release has been created on http://www.dockerhub.com. The `latest` tag should have been updated - creation time should be the current time. Depending on the type of release, the `3.2.1`, the `3.2` or the `3` tags should be the current date. * Check on dockerhub that tags that should not have been updated do not have the current time as creation time. - * Run the docker image using `docker run -p 8080:8080 wearefrank/webformulierenverwerker:3.2.1`. Check the name of the docker container you started using `docker ps -a`. Login to the docker container using `docker exec -it bash`. Check that `/opt/frank/resources/BuildInfo.property` contains the right version and the right date. + * Run the docker image using `docker run -p 8080:8080 wearefrank/webformulierenverwerker:3.2.1`. Check the name of the docker container you started using `docker ps -a`. Login to the docker container using `docker exec -it bash`. Check that `/opt/frank/resources/BuildInfo.property` and `/opt/frank/configurations/WebformulierenVerwerker/BuildInfo.properties` contain the right version and the right date. * Check a breaking change like above. This should update the major version. * Do a commit with \[skip ci\] in the commit message. It should not make a release and it should not push a docker image. * Make a pull request. Check that no release is made and that no docker image is pushed. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1fe2471..e93745d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ COPY --chown=tomcat lib/server/ /usr/local/tomcat/lib/ COPY --chown=tomcat java /tmp/java RUN javac \ /tmp/java/nl/nn/adapterframework/http/HttpSenderBase.java \ + /tmp/java/nl/nn/adapterframework/http/HttpSessionBase.java \ -classpath "/usr/local/tomcat/webapps/ROOT/WEB-INF/lib/*:/usr/local/tomcat/lib/*" \ -verbose -d /usr/local/tomcat/webapps/ROOT/WEB-INF/classes RUN rm -rf /tmp/java diff --git a/README.md b/README.md index 9f93840..1aa7dea 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # WebformulierenVerwerker -Documentation can be found in Jira > Gemeente Team > [GT-36](https://wearefrank-devops.atlassian.net/browse/GT-36). +Communicate between Kodison and Corsa. This configuration transforms incoming Kodison messages and makes them into Corsa requests. The responses are then transformed into messages accepted by Kodison. -Please build docker image as follows: - -``` -docker build -t sudwestfryslan.nl/webformulierenverwerker . -``` +This application processes SOAP requests that satisfy a WSDL that has been copied from the predecessor of this bridge. The WSDL is included in this project. diff --git a/classes/BuildInfo.properties b/classes/BuildInfo.properties index b65591b..4188c98 100644 --- a/classes/BuildInfo.properties +++ b/classes/BuildInfo.properties @@ -1,2 +1,2 @@ -instance.version=6.2.14 -versionDate_ddmmyyyy=29/06/2023 +instance.version=6.5.8 +versionDate_ddmmyyyy=05/07/2023 diff --git a/configurations/WebformulierenVerwerker/BuildInfo.properties b/configurations/WebformulierenVerwerker/BuildInfo.properties index 5eba1b1..8ed2145 100644 --- a/configurations/WebformulierenVerwerker/BuildInfo.properties +++ b/configurations/WebformulierenVerwerker/BuildInfo.properties @@ -1,2 +1,2 @@ -configuration.version=6.1.7 -configuration.timestamp=20230626-145819 +configuration.version=6.5.8 +configuration.timestamp=20230705-073540 diff --git a/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerker.xml b/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerker.xml index ffdb543..1f86e8b 100644 --- a/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerker.xml +++ b/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerker.xml @@ -12,16 +12,24 @@ - + - + + + + + + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -82,7 +90,7 @@ - + @@ -90,9 +98,15 @@ - + + + + + + + @@ -101,7 +115,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -113,22 +131,37 @@ - + + + + + + + - + + + + + + + + + + @@ -141,7 +174,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -177,14 +214,18 @@ - + - + + + + + @@ -194,7 +235,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -220,7 +265,7 @@ - + @@ -228,9 +273,14 @@ - + + + + + + @@ -239,7 +289,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -256,7 +310,7 @@ - + @@ -264,9 +318,14 @@ - + + + + + + @@ -275,7 +334,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -285,7 +348,7 @@ - + @@ -293,9 +356,14 @@ - + + + + + + @@ -304,7 +372,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -325,9 +397,11 @@ - + + + @@ -351,14 +425,19 @@ - + - + + + + + + @@ -367,7 +446,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -379,40 +462,57 @@ - + - + + query="SELECT VERTROUWELIJKHEID, AFZENDER, ONDERWERP FROM INFO_CACHE WHERE REGISTRATIENUMMER = ?" /> - + - + + + + + + + + + + + + + - + - + + + + + + @@ -421,7 +521,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -437,7 +541,8 @@ - + @@ -449,9 +554,14 @@ - + + + + + + @@ -460,7 +570,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -474,7 +588,8 @@ - + @@ -482,9 +597,14 @@ - + + + + + + @@ -493,7 +613,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -507,7 +631,7 @@ - + @@ -515,9 +639,14 @@ - + + + + + + @@ -526,7 +655,11 @@ + styleSheetName="xsl/ParseNegativeHttpResultNew.xsl" + getInputFromFixedValue="<dummy/>"> + + + @@ -545,4 +678,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerkerDispatcher.xml b/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerkerDispatcher.xml index 394311d..af41b10 100644 --- a/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerkerDispatcher.xml +++ b/configurations/WebformulierenVerwerker/Configuration_WebformulierenVerwerkerDispatcher.xml @@ -24,7 +24,7 @@ Attribuut name is nodig om een zinnige operation name=... te krijgen in de WSDL @@ -36,12 +36,11 @@ Attribuut name is nodig om een zinnige operation name=... te krijgen in de WSDL which is working for WsdlXmlInputValidator above. --> - @@ -57,6 +56,7 @@ Attribuut name is nodig om een zinnige operation name=... te krijgen in de WSDL xpathExpression="name(*)"> + @@ -72,6 +72,10 @@ Attribuut name is nodig om een zinnige operation name=... te krijgen in de WSDL + + + + @@ -95,7 +99,17 @@ Attribuut name is nodig om een zinnige operation name=... te krijgen in de WSDL javaListener="opslaanInkNatuurlijkPersoon"> - + + + + + + + + + + + INK + + + + + + poststuk.dat_afh_str + + + + + + + poststuk.onderwerp + + + + + + poststuk.inhoud1 + + + + + + poststuk.v_plaats_id + + + + + + poststuk.reg_datum + + + + + + poststuk.dat_poststuk + + + + + + poststuk.kenmerk + + + + + + obj_vert.vertrouw_id + + + + + + poststuk.soort_ext + I + + + poststuk.relatie_id + + + + + + + + + + + ontvdat + + + + + + kanaal + Internet + + + + + + + + + \ No newline at end of file diff --git a/configurations/WebformulierenVerwerker/xsl/Message2CreateMetaDocument.xsl b/configurations/WebformulierenVerwerker/xsl/Message2CreateMetaDocument.xsl index e1b7cdc..79bfd06 100644 --- a/configurations/WebformulierenVerwerker/xsl/Message2CreateMetaDocument.xsl +++ b/configurations/WebformulierenVerwerker/xsl/Message2CreateMetaDocument.xsl @@ -4,6 +4,10 @@ + + + + @@ -33,7 +37,7 @@ poststuk.onderwerp - + @@ -45,7 +49,7 @@ poststuk.v_plaats_id - + @@ -72,6 +76,13 @@ + + poststuk.relatie_id + + + + + diff --git a/configurations/WebformulierenVerwerker/xsl/Message2QueryCompany.xsl b/configurations/WebformulierenVerwerker/xsl/Message2QueryCompany.xsl new file mode 100644 index 0000000..9540370 --- /dev/null +++ b/configurations/WebformulierenVerwerker/xsl/Message2QueryCompany.xsl @@ -0,0 +1,28 @@ + + + + + + + E + + + qrtReference + BRSkvk + qcAnd + qoEqual + + + + + + false + false + false + false + true + true + 1 + + + \ No newline at end of file diff --git a/configurations/WebformulierenVerwerker/xsl/ParseNegativeHttpResultNew.xsl b/configurations/WebformulierenVerwerker/xsl/ParseNegativeHttpResultNew.xsl new file mode 100644 index 0000000..a78a584 --- /dev/null +++ b/configurations/WebformulierenVerwerker/xsl/ParseNegativeHttpResultNew.xsl @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/configurations/WebformulierenVerwerker/xsl/Response2NoXMLCompany.xsl b/configurations/WebformulierenVerwerker/xsl/Response2NoXMLCompany.xsl new file mode 100644 index 0000000..79c05d8 --- /dev/null +++ b/configurations/WebformulierenVerwerker/xsl/Response2NoXMLCompany.xsl @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index ee62576..4cc26ce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,16 +3,16 @@ version: '3.8' services: frank: build: . - image: wearefrank/webformulierenverwerker:latest + image: wearefrank/webformulierenverwerker:${VERSION:-latest} ports: - "${PORT:-8080}:8080" environment: - application.server.type.custom: NARAYANA + application.server.type.custom: ${TRANSACTION_MANAGER:-NARAYANA} credentialFactory.class: nl.nn.credentialprovider.PropertyFileCredentialFactory credentialFactory.map.properties: /opt/frank/secrets/credentials.properties env_file: - .env volumes: - - ./secrets:/opt/frank/secrets + - ${SECRETS_PATH:-./secrets}:/opt/frank/secrets restart: unless-stopped diff --git a/docs/TestWebformulierenVerwerkerCorsa-soapui-project.xml b/docs/TestWebformulierenVerwerkerCorsa-soapui-project.xml index f9447d9..029d387 100644 --- a/docs/TestWebformulierenVerwerkerCorsa-soapui-project.xml +++ b/docs/TestWebformulierenVerwerkerCorsa-soapui-project.xml @@ -13751,7 +13751,7 @@ -]]>http://schemas.xmlsoap.org/wsdl/http://swfkvt01/wsCorsa7/Corsa72WS4j.asmxSEQUENTIALPersoonIDSP.00132223BSN900246315DocumentIDI23.080502BijlageIDBIJ.361313CorsaInbox${TestWebformulierenVerwerkerCorsa.CorsaInbox}CORSA72WS4jSoap12About<xml-fragment/>UTF-8${#Project#CorsaWebService}\r +]]>http://schemas.xmlsoap.org/wsdl/http://swfkvt01/wsCorsa7/Corsa72WS4j.asmxSEQUENTIALPersoonIDSP.00135581BSN900246315DocumentIDI23.800002BijlageIDBIJ.430003CorsaInbox${TestWebformulierenVerwerkerCorsa.CorsaInbox}CORSA72WS4jSoap12About<xml-fragment/>UTF-8${#Project#CorsaWebService}\r \r \r \r @@ -13760,8 +13760,8 @@ - KodsionformulierenNaarCorsa - corsaswf + WebformulierenVerwerker + ${#Project#CorsaConnectionID} ${#Project#CorsaUserName} ${#Project#CorsaPassword} false @@ -14131,4 +14131,385 @@ \r \r \r -]]>No AuthorizationCorsaWebService${#Project#CorsaWebService.Net}CorsaWebService.Java${#Project#CorsaServer}/wsCorsa7/Corsa72WS4j.asmxCorsaWebService.Net${#Project#CorsaServer}/wsCorsa7/Corsa72WS.asmxCorsaServer${TestWebformulierenVerwerkerCorsa.CorsaServer}CorsaUserName${TestWebformulierenVerwerkerCorsa.CorsaUserName}CorsaPassword${TestWebformulierenVerwerkerCorsa.CorsaPassword} \ No newline at end of file +]]>No AuthorizationBedrijfIDSO.00002414KVK01109553DocumentIDI23.800004BijlageIDBIJ.430004CorsaInbox${TestWebformulierenVerwerkerCorsa.CorsaInbox}CORSA72WS4jSoap12About<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r +]]>No AuthorizationCORSA72WS4jSoap12Connect<xml-fragment/>UTF-8${#Project#CorsaWebService} + + + + WebformulierenVerwerker + ${#Project#CorsaConnectionID} + ${#Project#CorsaUserName} + ${#Project#CorsaPassword} + false + + +]]>No AuthorizationCORSA72WS4jSoap12QueryExecute<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r + E\r + \r + \r + \r + \r + qrtReference\r + + BRSkvk + \r + qcAnd\r + \r + qoEqual\r + ${Properties#KVK} + \r + \r + \r + false\r + \r + false\r + \r + false\r + \r + false\r + \r + true\r + \r + true\r + \r + 0\r + \r + \r + \r + \r +]]>No AuthorizationPersoonIDResponse03 - QueryExecute - bevraag kvk//QueryExecResult/*[1]BedrijfIDPropertiestrueCORSA72WS4jSoap12CreateMetaPerson<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + + ${Properties#BedrijfID}\r + + + + VES + \r + \r + \r + \r + \r + \r + \r + + BRSkvk + \r + ${Properties#KVK}\r + \r + \r + \r + \r +]]>No AuthorizationPersoonIDResponse06 - CreateMetaPerson - voeg kvk toedeclare namespace bct="http://bct.nl"; +//bct:NewObjectID[1]BedrijfIDPropertiestrueCORSA72WS4jSoap12CreateMetaDocument<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r + \r + \r + INK\r + \r + \r + \r + + \r + poststuk.dat_afh_str\r + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())}\r + \r + + poststuk.onderwerp + onderwerp + + + poststuk.inhoud1 + text text text + + + poststuk.v_plaats_id + ${Properties#CorsaInbox} + + + poststuk.reg_datum + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())} + + + poststuk.dat_poststuk + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())} + + + poststuk.kenmerk + onderwerp + + + obj_vert.vertrouw_id + V-int + + + poststuk.soort_ext + P + + + poststuk.relatie_id + ${Properties#BedrijfID} + + \r + \r + \r + \r + + \r + ontvdat\r + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd-MM-yyyy").format(new Date())} + \r + + kanaal + Internet + + \r + \r + \r + \r + \r + \r + \r +]]>No AuthorizationObjectIDResponse08 - CreateMetaDocument - voeg documentregistratie toedeclare namespace bct="http://bct.nl"; +//bct:NewObjectID[1]DocumentIDPropertiestrueCORSA72WS4jSoap12CreateFileVersion<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r + S\r + \r + ${Properties#DocumentID}\r + \r + iVBORw0KGgoAAAANSUhEUgAABAAAAAC3CAYAAACMnSxdAAAAAXNSR0IArs4c6QAAQABJREFUeAHsnQd8HNW1uO/MNvXq3g3YgA0Gh14CNjUQIIQXO3n5Qx4lMWBj3AgkeSGIvLyEUA2mPCAhJDxS7BQghJQXwISe0I3pxRhjwEXNkiVtmfl/Z1eSV9I2yVprVzqj32hnZ+7c8t27M/ece+65xuimBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACWRIwMow3KANVmOMfbg5flSB8QyLmNYRrvHuZht3kmPMWMu4wwFUYRmrxOUTCGV8LphtHr530ALRgimBIU7gmpdNcaChZKLx+Ca7lj3Ddc00yzZjjetM8AWsYaGQucKqrvqfi6e80zbEUWnxlYASUAJKQAkoASWgBPKMgDfP8rvT2f27ObbaMfZ42zj72cYczr6/a8JjIibscY3t4bww8XLei7DPOdfmk6+uXWC8VtA4JTudCY1ACSiB3CHgGuvmh0urwkWBwyzjnG62m6OMzy12XNfL94Bl8Y1jYywvigAbBWFhiTc85JWnuVOBmhMloASUgBJQAkpACSiBTAkMegXAU2ZOYdg0TXZM6GiE+VONcfbyGqeAznwASAUI93xaHgEmPXq+d9s6+vnYAXAlbNyOE93C6VcloATyjcDyJ6sPsp52Fzgl7jGWE+Z5YJXwECiUcsR+8bESdRzLj98JG/PButh5/a8ElIASUAJKQAkoASWgBPKJwKBUALhmjme1qd/XNpHPh0ztZ+nKD3NNpIpPTPqtYoT8PgnxohywjdVTR5BPNa55VQJKwNz4VNXhYFjius4efE7AzF+eD+kfDDw5sAjQZ4C2ISWgBJSAElACSkAJKIG8JDCoFAD/MCcNd03whEfN1tMQ1PeMGDOavno1OyP8MZlfe+552U4100qgXwjc9PiI/Vxv5FLm888gwikI82IJlMj0J3pa/ykBJaAElIASUAJKQAkogcFE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\r + ftNative\r + 0\r + \r + .png\r + \r + Het swf logo\r + \r + Eerste versie\r + \r + + \r + \r +]]>No AuthorizationCORSA72WS4jSoap12CreateMetaDocument<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r + \r + \r + BIJL\r + \r + \r + + \r + poststuk.dat_afh_str\r + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())}\r + \r + + poststuk.onderwerp + onderwerp + + + poststuk.inhoud1 + text text text + + + poststuk.v_plaats_id + ${Properties#CorsaInbox} + + + poststuk.reg_datum + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())} + + + poststuk.dat_poststuk + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd/MM/yyyy").format(new Date())} + + + poststuk.kenmerk + onderwerp + + + obj_vert.vertrouw_id + V-int + + \r + \r + \r + \r + + \r + ontvdat\r + ${=import java.text.SimpleDateFormat; new SimpleDateFormat("dd-MM-yyyy").format(new Date())} + \r + + kanaal + Internet + + \r + \r + \r + \r + \r + \r + \r +]]>No AuthorizationObjectIDResponse11 - CreateMetaDocument - voeg bijlage toedeclare namespace bct="http://bct.nl"; +//bct:NewObjectID[1]BijlageIDPropertiestrueCORSA72WS4jSoap12CreateFileVersion<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r + S\r + \r + ${Properties#BijlageID}\r + \r + iVBORw0KGgoAAAANSUhEUgAABAAAAAC3CAYAAACMnSxdAAAAAXNSR0IArs4c6QAAQABJREFUeAHsnQd8HNW1uO/MNvXq3g3YgA0Gh14CNjUQIIQXO3n5Qx4lMWBj3AgkeSGIvLyEUA2mPCAhJDxS7BQghJQXwISe0I3pxRhjwEXNkiVtmfl/Z1eSV9I2yVprVzqj32hnZ+7c8t27M/ece+65xuimBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACSgBJaAElIASUAJKQAkoASWgBJSAElACSkAJKAEloASUgBJQAkpACWRIwMow3KANVmOMfbg5flSB8QyLmNYRrvHuZht3kmPMWMu4wwFUYRmrxOUTCGV8LphtHr530ALRgimBIU7gmpdNcaChZKLx+Ca7lj3Ddc00yzZjjetM8AWsYaGQucKqrvqfi6e80zbEUWnxlYASUAJKQAkoASWgBPKMgDfP8rvT2f27ObbaMfZ42zj72cYczr6/a8JjIibscY3t4bww8XLei7DPOdfmk6+uXWC8VtA4JTudCY1ACSiB3CHgGuvmh0urwkWBwyzjnG62m6OMzy12XNfL94Bl8Y1jYywvigAbBWFhiTc85JWnuVOBmhMloASUgBJQAkpACSiBTAkMegXAU2ZOYdg0TXZM6GiE+VONcfbyGqeAznwASAUI93xaHgEmPXq+d9s6+vnYAXAlbNyOE93C6VcloATyjcDyJ6sPsp52Fzgl7jGWE+Z5YJXwECiUcsR+8bESdRzLj98JG/PButh5/a8ElIASUAJKQAkoASWgBPKJwKBUALhmjme1qd/XNpHPh0ztZ+nKD3NNpIpPTPqtYoT8PgnxohywjdVTR5BPNa55VQJKwNz4VNXhYFjius4efE7AzF+eD+kfDDw5sAjQZ4C2ISWgBJSAElACSkAJKIG8JDCoFAD/MCcNd03whEfN1tMQ1PeMGDOavno1OyP8MZlfe+552U4100qgXwjc9PiI/Vxv5FLm888gwikI82IJlMj0J3pa/ykBJaAElIASUAJKQAkogcFE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 + ftNative\r + 0\r + \r + .png\r + \r + Het swf logo\r + \r + Eerste versie\r + \r + + \r + \r +]]>No AuthorizationCORSA72WS4jSoap12ModObjectRelation<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + MORA_Create\r + \r + S\r + \r + ${Properties#BijlageID}\r + \r + S\r + \r + ${Properties#DocumentID}\r + \r + BIJ\r + \r + \r +]]>No AuthorizationCORSA72WS4jSoap12Disconnect<xml-fragment/>UTF-8${#Project#CorsaWebService}\r + \r + \r + \r + \r +]]>No AuthorizationCorsaWebService${#Project#CorsaWebService.Net}CorsaWebService.Java${#Project#CorsaServer}/wsCorsa7/Corsa72WS4j.asmxCorsaWebService.Net${#Project#CorsaServer}/wsCorsa7/Corsa72WS.asmxCorsaServer${TestWebformulierenVerwerkerCorsa.CorsaServer}CorsaUserName${TestWebformulierenVerwerkerCorsa.CorsaUserName}CorsaPassword${TestWebformulierenVerwerkerCorsa.CorsaPassword}CorsaConnectionID${TestWebformulierenVerwerkerCorsa.CorsaConnectionID} \ No newline at end of file diff --git a/frank_update.sh b/frank_update.sh new file mode 100755 index 0000000..9008b43 --- /dev/null +++ b/frank_update.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +git pull + +docker compose down + +docker compose pull + +docker compose up -d \ No newline at end of file diff --git a/java/nl/nn/adapterframework/http/HttpSenderBase.java b/java/nl/nn/adapterframework/http/HttpSenderBase.java index 86823fc..bc64bce 100644 --- a/java/nl/nn/adapterframework/http/HttpSenderBase.java +++ b/java/nl/nn/adapterframework/http/HttpSenderBase.java @@ -21,81 +21,46 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerConfigurationException; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.MethodNotSupportedException; import org.apache.http.StatusLine; -import org.apache.http.auth.AuthProtocolState; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.AuthSchemes; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import lombok.Getter; +import lombok.Setter; import nl.nn.adapterframework.configuration.ConfigurationException; import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.core.CanUseSharedResource; import nl.nn.adapterframework.core.HasPhysicalDestination; +import nl.nn.adapterframework.core.ISenderWithParameters; import nl.nn.adapterframework.core.ParameterException; import nl.nn.adapterframework.core.PipeLineSession; import nl.nn.adapterframework.core.Resource; import nl.nn.adapterframework.core.SenderException; import nl.nn.adapterframework.core.SenderResult; import nl.nn.adapterframework.core.TimeoutException; -import nl.nn.adapterframework.encryption.AuthSSLContextFactory; -import nl.nn.adapterframework.encryption.HasKeystore; -import nl.nn.adapterframework.encryption.HasTruststore; import nl.nn.adapterframework.encryption.KeystoreType; -import nl.nn.adapterframework.http.authentication.AuthenticationScheme; -import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; -import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; -import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; -import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; import nl.nn.adapterframework.parameters.Parameter; +import nl.nn.adapterframework.parameters.ParameterList; import nl.nn.adapterframework.parameters.ParameterValue; import nl.nn.adapterframework.parameters.ParameterValueList; -import nl.nn.adapterframework.senders.SenderWithParametersBase; import nl.nn.adapterframework.stream.Message; import nl.nn.adapterframework.task.TimeoutGuard; import nl.nn.adapterframework.util.AppConstants; import nl.nn.adapterframework.util.ClassUtils; -import nl.nn.adapterframework.util.CredentialFactory; import nl.nn.adapterframework.util.StreamUtil; import nl.nn.adapterframework.util.TransformerPool; import nl.nn.adapterframework.util.XmlUtils; @@ -114,55 +79,6 @@ * another_param_name=another_param_value * * - *

- * Note 1: - * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like - * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. - * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') - *

- * Replace in the directory java\jre\lib\security the following files: - *
    - *
  • local_policy.jar
  • - *
  • US_export_policy.jar
  • - *
- *

- * Note 2: - * To debug ssl-related problems, set the following system property: - *

    - *
  • IBM / WebSphere: -Djavax.net.debug=true
  • - *
  • SUN: -Djavax.net.debug=all
  • - *
- *

- *

- * Note 3: - * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, - * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: - *

    - *
  • open the URL you are trying to reach in InternetExplorer
  • - *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • - *
  • click on tab 'Certificeringspad'
  • - *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • - *
  • click on tab 'Details'
  • - *
  • click on 'Kopieren naar bestand'
  • - *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • - *
  • click 'next', choose a filename
  • - *
  • click 'next' and 'finish'
  • - *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • - *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • - *
  • add the generated certificate (Toevoegen...)
  • - *
  • store the key-database in JKS format
  • - *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • - *
  • use jks for the truststoreType-attribute
  • - *
  • restart your application
  • - *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: - * keytool -import -alias yourAlias -file pathToSavedCertificate
  • - *
- *

- * Note 4: - * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, - * please check password or authAlias configuration of the corresponding certificate. - *

- * * @ff.parameters Any parameters present are appended to the request (when method is GET as request-parameters, when method POST as body part) except the headersParams list, which are added as HTTP headers, and the urlParam header * @ff.forward "<statusCode of the HTTP response>" default * @@ -171,15 +87,17 @@ */ //TODO: Fix javadoc! -public abstract class HttpSenderBase extends SenderWithParametersBase implements HasPhysicalDestination, HasKeystore, HasTruststore { +public abstract class HttpSenderBase extends HttpSessionBase implements HasPhysicalDestination, ISenderWithParameters, CanUseSharedResource { - private final String CONTEXT_KEY_STATUS_CODE="Http.StatusCode"; - private final String CONTEXT_KEY_REASON_PHRASE="Http.ReasonPhrase"; + private static final String CONTEXT_KEY_STATUS_CODE = "Http.StatusCode"; + private static final String CONTEXT_KEY_REASON_PHRASE = "Http.ReasonPhrase"; public static final String MESSAGE_ID_HEADER = "Message-Id"; public static final String CORRELATION_ID_HEADER = "Correlation-Id"; private final @Getter(onMethod = @__(@Override)) String domain = "Http"; + private @Setter String sharedResourceRef; + private @Getter String url; private @Getter String urlParam = "url"; @@ -192,121 +110,53 @@ public enum HttpMethod { private @Getter ContentType fullContentType = null; private @Getter String contentType = null; - /* CONNECTION POOL */ - private @Getter int timeout = 10000; - private @Getter int maxConnections = 10; - private @Getter int maxExecuteRetries = 1; - private @Getter boolean staleChecking=true; - private @Getter int staleTimeout = 5000; // [ms] - private @Getter int connectionTimeToLive = 900; // [s] - private @Getter int connectionIdleTimeout = 10; // [s] - private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - private @Getter HttpClientContext httpClientContext = HttpClientContext.create(); - private @Getter CloseableHttpClient httpClient; - - /* SECURITY */ - private @Getter String authAlias; - private @Getter String username; - private @Getter String password; - private @Getter String authDomain; - private @Getter String tokenEndpoint; - private @Getter int tokenExpiry=-1; - private @Getter String clientAuthAlias; - private @Getter String clientId; - private @Getter String clientSecret; - private @Getter String scope; - private @Getter boolean authenticatedTokenRequest; - - /* PROXY */ - private @Getter String proxyHost; - private @Getter int proxyPort=80; - private @Getter String proxyAuthAlias; - private @Getter String proxyUsername; - private @Getter String proxyPassword; - private @Getter String proxyRealm=null; - - /* SSL */ - private @Getter String keystore; - private @Getter String keystoreAuthAlias; - private @Getter String keystorePassword; - private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; - private @Getter String keystoreAlias; - private @Getter String keystoreAliasAuthAlias; - private @Getter String keystoreAliasPassword; - private @Getter String keyManagerAlgorithm=null; - - private @Getter String truststore=null; - private @Getter String truststoreAuthAlias; - private @Getter String truststorePassword=null; - private @Getter KeystoreType truststoreType=KeystoreType.JKS; - private @Getter String trustManagerAlgorithm=null; - private @Getter boolean allowSelfSignedCertificates = false; - private @Getter boolean verifyHostname=true; - private @Getter boolean ignoreCertificateExpiredException=false; - private @Getter String headersParams=""; - private @Getter boolean followRedirects=true; - private @Getter boolean ignoreRedirects=false; private @Getter boolean xhtml=false; private @Getter String styleSheetName=null; - private @Getter String protocol=null; private @Getter String resultStatusCodeSessionKey; private @Getter String parametersToSkipWhenEmpty; private final boolean APPEND_MESSAGEID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.messageid", true); private final boolean APPEND_CORRELATIONID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.correlationid", true); - private boolean disableCookies = false; private TransformerPool transformerPool=null; protected Parameter urlParameter; protected URI staticUri; - private CredentialFactory credentials; - private CredentialFactory user_cf; - private CredentialFactory client_cf; protected Set requestOrBodyParamsSet=new HashSet<>(); protected Set headerParamsSet=new LinkedHashSet<>(); protected Set parametersToSkipWhenEmptySet=new HashSet<>(); - /** - * Makes sure only http(s) requests can be performed. - */ - protected URI getURI(String url) throws URISyntaxException { - URIBuilder uri = new URIBuilder(url); - - if(uri.getScheme() == null) { - throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); - } - if (!uri.getScheme().matches("(?i)https?")) { - throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); - } + protected ParameterList paramList = null; - if (uri.getPath()==null) { - uri.setPath("/"); + @Override + public void addParameter(Parameter p) { + if (paramList==null) { + paramList=new ParameterList(); } + paramList.add(p); + } - log.info(getLogPrefix()+"created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); - return uri.build(); + /** + * return the Parameters + */ + @Override + public ParameterList getParameterList() { + return paramList; } @Override public void configure() throws ConfigurationException { - super.configure(); - - /** - * TODO find out if this really breaks proxy authentication or not. - */ -// httpClientBuilder.disableAuthCaching(); - - Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setConnectTimeout(getTimeout()); - requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); - requestConfigBuilder.setSocketTimeout(getTimeout()); + if(StringUtils.isBlank(sharedResourceRef)) { + log.info("configuring local HttpSession"); + super.configure(); + } if (paramList!=null) { paramList.configure(); + if (StringUtils.isNotEmpty(getHeadersParams())) { StringTokenizer st = new StringTokenizer(getHeadersParams(), ","); while (st.hasMoreElements()) { @@ -348,10 +198,6 @@ public void configure() throws ConfigurationException { } } - if (getMaxConnections() <= 0) { - throw new ConfigurationException(getLogPrefix()+"maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); - } - try { if (urlParameter == null) { if (StringUtils.isEmpty(getUrl())) { @@ -360,102 +206,32 @@ public void configure() throws ConfigurationException { staticUri = getURI(getUrl()); } } catch (URISyntaxException e) { - throw new ConfigurationException(getLogPrefix()+"cannot interpret url ["+getUrl()+"]", e); - } - - AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); - - if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { - user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); - credentials = user_cf; - } - client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); - if (credentials==null) { - credentials = client_cf; - } - if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { - throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); - } - HttpHost proxy = null; - CredentialFactory pcf = null; - if (StringUtils.isNotEmpty(getProxyHost())) { - proxy = new HttpHost(getProxyHost(), getProxyPort()); - pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); - requestConfigBuilder.setProxy(proxy); - httpClientBuilder.setProxy(proxy); - } - - try { - setupAuthentication(pcf, proxy, requestConfigBuilder); - } catch (HttpAuthenticationException e) { - throw new ConfigurationException("exception configuring authentication", e); + throw new ConfigurationException("cannot interpret url ["+getUrl()+"]", e); } if (StringUtils.isNotEmpty(getStyleSheetName())) { try { Resource stylesheet = Resource.getResource(this, getStyleSheetName()); if (stylesheet == null) { - throw new ConfigurationException(getLogPrefix() + "cannot find stylesheet ["+getStyleSheetName()+"]"); + throw new ConfigurationException("cannot find stylesheet ["+getStyleSheetName()+"]"); } transformerPool = TransformerPool.getInstance(stylesheet); } catch (IOException e) { - throw new ConfigurationException(getLogPrefix() + "cannot retrieve ["+ getStyleSheetName() + "]", e); + throw new ConfigurationException("cannot retrieve ["+ getStyleSheetName() + "]", e); } catch (TransformerConfigurationException te) { - throw new ConfigurationException(getLogPrefix() + "got error creating transformer from file [" + getStyleSheetName() + "]", te); + throw new ConfigurationException("got error creating transformer from file [" + getStyleSheetName() + "]", te); } } - - httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); - - httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); - - if(areCookiesDisabled()) { - httpClientBuilder.disableCookieManagement(); - } - - // The redirect strategy used to only redirect GET, DELETE and HEAD. - httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() { - @Override - protected boolean isRedirectable(String method) { - return isFollowRedirects(); - } - }); } @Override public void open() throws SenderException { - // In order to support multiThreading and connectionPooling - // If a sslSocketFactory has been defined, the connectionManager has to be initialized with the sslSocketFactory - PoolingHttpClientConnectionManager connectionManager; - int timeToLive = getConnectionTimeToLive(); - if (timeToLive<=0) { - timeToLive = -1; - } - SSLConnectionSocketFactory sslSocketFactory = getSSLConnectionSocketFactory(); - if(sslSocketFactory != null) { - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); - connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); - } - else { - connectionManager = new PoolingHttpClientConnectionManager(timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created default PoolingHttpClientConnectionManager"); - } - - connectionManager.setMaxTotal(getMaxConnections()); - connectionManager.setDefaultMaxPerRoute(getMaxConnections()); - - if (isStaleChecking()) { - log.info(getLogPrefix()+"set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); - connectionManager.setValidateAfterInactivity(getStaleTimeout()); + try { + start(); + } catch (Exception e) { + throw new SenderException(getLogPrefix()+"unable to create HttpClient", e); } - httpClientBuilder.setConnectionManager(connectionManager); - httpClientBuilder.evictIdleConnections(getConnectionIdleTimeout(), TimeUnit.SECONDS); - if (transformerPool!=null) { try { transformerPool.open(); @@ -463,116 +239,36 @@ public void open() throws SenderException { throw new SenderException(getLogPrefix()+"cannot start TransformerPool", e); } } - - httpClient = httpClientBuilder.build(); } @Override - public void close() throws SenderException { - try { - //Close the HttpClient and ConnectionManager to release resources and potential open connections - if(httpClient != null) { - httpClient.close(); - } - } catch (IOException e) { - throw new SenderException(e); - } - - if (transformerPool!=null) { - transformerPool.close(); - } - } - - private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, Builder requestConfigBuilder) throws HttpAuthenticationException { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { - - credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); - - AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); - requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); - requestConfigBuilder.setAuthenticationEnabled(true); - - if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { - OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); - httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); - httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); - } - } - if (proxy!=null) { - AuthScope scope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); - - - if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { - Credentials credentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); - credentialsProvider.setCredentials(scope, credentials); - } - //log.trace("setting credentialProvider [" + credentialsProvider.toString() + "]"); - - if(prefillProxyAuthCache()) { - requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); - - AuthCache authCache = httpClientContext.getAuthCache(); - if(authCache == null) - authCache = new BasicAuthCache(); - - authCache.put(proxy, new BasicScheme()); - httpClientContext.setAuthCache(authCache); - } - - } - - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); - } - - private void preAuthenticate() { - if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { - AuthState authState = httpClientContext.getTargetAuthState(); - if (authState==null) { - authState = new AuthState(); - httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); - } - authState.setState(AuthProtocolState.CHALLENGED); - authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); - } + public Class getObjectType() { + return CloseableHttpClient.class; } - private Credentials getCredentials() { - String uname; - if (StringUtils.isNotEmpty(getAuthDomain())) { - uname = getAuthDomain() + "\\" + credentials.getUsername(); + @Override + public void start() { + if(StringUtils.isNotBlank(sharedResourceRef)) { + setHttpClient(getSharedResource(sharedResourceRef)); } else { - uname = credentials.getUsername(); + buildHttpClient(); } - - return new UsernamePasswordCredentials(uname, credentials.getPassword()); - } - - private AuthenticationScheme getPreferredAuthenticationScheme() { - return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; } - protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws SenderException { - SSLConnectionSocketFactory sslSocketFactory; - HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); - - try { - javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); - sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); - } catch (Exception e) { - throw new SenderException("cannot create or initialize SocketFactory", e); + @Override + public void close() { + if(StringUtils.isBlank(sharedResourceRef)) { + super.stop(); } - // This method will be overwritten by the connectionManager when connectionPooling is enabled! - // Can still be null when no default or an invalid system sslSocketFactory has been defined - if(sslSocketFactory != null) { - httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + if (transformerPool!=null) { + transformerPool.close(); } - return sslSocketFactory; } - protected boolean appendParameters(boolean parametersAppended, StringBuffer path, ParameterValueList parameters) throws SenderException { + protected boolean appendParameters(boolean parametersAppended, StringBuilder path, ParameterValueList parameters) throws SenderException { if (parameters != null) { - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending ["+parameters.size()+"] parameters"); + log.debug("appending [{}] parameters", parameters::size); for(ParameterValue pv : parameters) { if (requestOrBodyParamsSet.contains(pv.getName())) { String value = pv.asStringValue(""); @@ -586,7 +282,7 @@ protected boolean appendParameters(boolean parametersAppended, StringBuffer path } String parameterToAppend = pv.getDefinition().getName() +"="+ URLEncoder.encode(value, getCharSet()); - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending parameter ["+parameterToAppend+"]"); + log.debug("appending parameter [{}]", parameterToAppend); path.append(parameterToAppend); } catch (UnsupportedEncodingException e) { throw new SenderException(getLogPrefix()+"["+getCharSet()+"] encoding error. Failed to add parameter ["+pv.getDefinition().getName()+"]", e); @@ -598,6 +294,17 @@ protected boolean appendParameters(boolean parametersAppended, StringBuffer path return parametersAppended; } + + /** + * Returns the true name of the class and not XsltPipe$$EnhancerBySpringCGLIB$$563e6b5d. + * {@link ClassUtils#nameOf(Object)} makes sure the original class will be used. + * + * @return className + name of the ISender + */ + protected String getLogPrefix() { + return ClassUtils.nameOf(this) + " "; + } + /** * Custom implementation to create a {@link HttpRequestBase HttpRequest} object. * @param uri endpoint to send the message to @@ -690,17 +397,16 @@ public SenderResult sendMessage(Message message, PipeLineSession session) throws preAuthenticate(); - log.info(getLogPrefix()+"configured httpclient for host ["+targetUri.getHost()+"]"); + log.info("configured httpclient for host [{}]", targetUri::getHost); } catch (Exception e) { throw new SenderException(e); } - Message result = null; - int statusCode = -1; + Message result; + int statusCode; boolean success; - String reasonPhrase = null; - HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + String reasonPhrase; TimeoutGuard tg = new TimeoutGuard(1+getTimeout()/1000, getName()) { @@ -722,12 +428,13 @@ protected void abort() { } } if (httpClient == null) { + buildHttpClient(); // Prevent java.lang.IllegalStateException: Connection pool shut down httpClient = getHttpClient(); session.put("httpClient", httpClient); } - log.debug(getLogPrefix()+"executing method [" + httpRequestBase.getRequestLine() + "]"); - HttpResponse httpResponse = httpClient.execute(targetHost, httpRequestBase, httpClientContext); - log.debug(getLogPrefix()+"executed method"); + log.debug("executing method [{}]", httpRequestBase::getRequestLine); + HttpResponse httpResponse = execute(httpClient, targetUri, httpRequestBase); + log.debug("executed method"); HttpResponseHandler responseHandler = new HttpResponseHandler(httpResponse); StatusLine statusline = httpResponse.getStatusLine(); @@ -741,14 +448,14 @@ protected void abort() { // Only give warnings for 4xx (client errors) and 5xx (server errors) if (statusCode >= 400 && statusCode < 600) { - log.warn(getLogPrefix()+"status ["+statusline.toString()+"]"); + log.warn("status [{}]", statusline); } else { - log.debug(getLogPrefix()+"status ["+statusCode+"]"); + log.debug("status [{}]", statusCode); } result = extractResult(responseHandler, session); - log.debug(getLogPrefix()+"retrieved result ["+result+"]"); + log.debug("retrieved result [{}]", result); } catch (IOException e) { httpRequestBase.abort(); if (e instanceof SocketTimeoutException) { @@ -763,8 +470,8 @@ protected void abort() { // in a sessionKey for later use in the pipeline. // // IMPORTANT: It is possible that poorly written implementations - // wont read or close the response. - // This will cause the connection to become stale.. + // won't read or close the response. + // This will cause the connection to become stale. if (tg.cancel()) { throw new TimeoutException(getLogPrefix()+"timeout of ["+getTimeout()+"] ms exceeded"); @@ -776,15 +483,16 @@ protected void abort() { } if (isXhtml() && !Message.isEmpty(result)) { + // TODO: Streaming XHTML conversion for better performance with large result message? String xhtml; - try { - xhtml = XmlUtils.toXhtml(result); + try(Message m = result) { + xhtml = XmlUtils.toXhtml(m); } catch (IOException e) { throw new SenderException("error reading http response as String", e); } if (transformerPool != null && xhtml != null) { - log.debug(getLogPrefix() + " transforming result [" + xhtml + "]"); + log.debug("transforming result [{}]", xhtml); try { xhtml = transformerPool.transform(Message.asSource(xhtml)); } catch (Exception e) { @@ -795,7 +503,7 @@ protected void abort() { result = Message.asMessage(xhtml); } - if (result==null) { + if (result == null) { result = Message.nullMessage(); } log.debug("Storing [{}]=[{}], [{}]=[{}]", CONTEXT_KEY_STATUS_CODE, statusCode, CONTEXT_KEY_REASON_PHRASE, reasonPhrase); @@ -849,174 +557,18 @@ public void setCharSet(String string) { charSet = string; } - /** - * Timeout in ms of obtaining a connection/result. 0 means no timeout - * @ff.default 10000 - */ - public void setTimeout(int i) { - timeout = i; - } - - /** - * The maximum number of concurrent connections - * @ff.default 10 - */ - public void setMaxConnections(int i) { - maxConnections = i; - } - - /** - * The maximum number of times the execution is retried - * @ff.default 1 (for repeatable messages) else 0 - */ - public void setMaxExecuteRetries(int i) { - maxExecuteRetries = i; - } - - /** Authentication alias used for authentication to the host */ - public void setAuthAlias(String string) { - authAlias = string; - } - - /** Username used for authentication to the host */ - public void setUsername(String username) { - this.username = username; - } @Deprecated @ConfigurationWarning("Please use attribute username instead") public void setUserName(String username) { setUsername(username); } - /** Password used for authentication to the host */ - public void setPassword(String string) { - password = string; - } - - /** - * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
- *
- * Assuming the following user:
- * UPN: john.doe@CorpDomain.biz
- * sAMAccountName: CORPDOMAIN\john.doe
- *
- * The username attribute may be set to john.doe
- * The AuthDomain attribute may be set to CORPDOMAIN
- */ - @Deprecated - @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") - public void setAuthDomain(String string) { - authDomain = string; - } - - /** - * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, - * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests - * in an HTTP Header 'Authorization' with a 'Bearer' prefix. - */ - public void setTokenEndpoint(String string) { - tokenEndpoint = string; - } - /** - * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token - * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, - * or when the regular server returns a 401 status with a challenge. - * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. - * @ff.default -1 - */ - public void setTokenExpiry(int value) { - tokenExpiry = value; - } - /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ - public void setClientAlias(String clientAuthAlias) { - this.clientAuthAlias = clientAuthAlias; - } - /** Client_id used in authentication to tokenEndpoint */ - public void setClientId(String clientId) { - this.clientId = clientId; - } - - /** Client_secret used in authentication to tokenEndpoint */ - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ - public void setScope(String string) { - scope = string; - } - /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ - public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { - this.authenticatedTokenRequest = authenticatedTokenRequest; - } - - - /** Proxy host */ - public void setProxyHost(String string) { - proxyHost = string; - } - - /** - * Proxy port - * @ff.default 80 - */ - public void setProxyPort(int i) { - proxyPort = i; - } - - /** Alias used to obtain credentials for authentication to proxy */ - public void setProxyAuthAlias(String string) { - proxyAuthAlias = string; - } - - /** - * Proxy username - * @ff.default - */ - public void setProxyUsername(String string) { - proxyUsername = string; - } @Deprecated @ConfigurationWarning("Please use \"proxyUsername\" instead") public void setProxyUserName(String string) { setProxyUsername(string); } - /** - * Proxy password - * @ff.default - */ - public void setProxyPassword(String string) { - proxyPassword = string; - } - - /** - * Proxy realm - * @ff.default - */ - public void setProxyRealm(String string) { - proxyRealm = StringUtils.isNotEmpty(string) ? string : null; - } - - /** - * TODO: make this configurable - * @return false - */ - public boolean prefillProxyAuthCache() { - return false; - } - - /** - * Disables the use of cookies, making the sender completely stateless - * @ff.default false - */ - public void setDisableCookies(boolean disableCookies) { - this.disableCookies = disableCookies; - } - public boolean areCookiesDisabled() { - return disableCookies; - } - - @Deprecated @ConfigurationWarning("Please use attribute keystore instead") public void setCertificate(String string) { @@ -1038,87 +590,6 @@ public void setCertificatePassword(String string) { setKeystorePassword(string); } - /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ - @Override - public void setKeystore(String string) { - keystore = string; - } - - @Override - public void setKeystoreType(KeystoreType value) { - keystoreType = value; - } - - @Override - public void setKeystoreAuthAlias(String string) { - keystoreAuthAlias = string; - } - - @Override - public void setKeystorePassword(String string) { - keystorePassword = string; - } - - @Override - public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { - this.keyManagerAlgorithm = keyManagerAlgorithm; - } - - @Override - public void setKeystoreAlias(String string) { - keystoreAlias = string; - } - @Override - public void setKeystoreAliasAuthAlias(String string) { - keystoreAliasAuthAlias = string; - } - @Override - public void setKeystoreAliasPassword(String string) { - keystoreAliasPassword = string; - } - - @Override - /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ - public void setTruststore(String string) { - truststore = string; - } - - @Override - public void setTruststoreAuthAlias(String string) { - truststoreAuthAlias = string; - } - - @Override - public void setTruststorePassword(String string) { - truststorePassword = string; - } - - @Override - public void setTruststoreType(KeystoreType value) { - truststoreType = value; - } - - @Override - public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { - this.trustManagerAlgorithm = trustManagerAlgorithm; - } - - @Override - public void setVerifyHostname(boolean b) { - verifyHostname = b; - } - - @Override - public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { - this.allowSelfSignedCertificates = allowSelfSignedCertificates; - } - - @Override - public void setIgnoreCertificateExpiredException(boolean b) { - ignoreCertificateExpiredException = b; - } - - /** Comma separated list of parameter names which should be set as HTTP headers */ public void setHeadersParams(String headersParams) { this.headersParams = headersParams; @@ -1129,56 +600,6 @@ public void setParametersToSkipWhenEmpty(String parametersToSkipWhenEmpty) { this.parametersToSkipWhenEmpty = parametersToSkipWhenEmpty; } - - /** - * If true, a redirect request will be honoured, e.g. to switch to HTTPS - * @ff.default true - */ - public void setFollowRedirects(boolean b) { - followRedirects = b; - } - - /** - * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful - * @ff.default false - */ - public void setIgnoreRedirects(boolean b) { - ignoreRedirects = b; - } - - - /** - * Controls whether connections checked to be stale, i.e. appear open, but are not. - * @ff.default true - */ - public void setStaleChecking(boolean b) { - staleChecking = b; - } - - /** - * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. - * @ff.default 5000 ms - */ - public void setStaleTimeout(int timeout) { - staleTimeout = timeout; - } - - /** - * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. - * @ff.default 900 s - */ - public void setConnectionTimeToLive(int timeToLive) { - connectionTimeToLive = timeToLive; - } - - /** - * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool - * @ff.default 10 s - */ - public void setConnectionIdleTimeout(int idleTimeout) { - connectionIdleTimeout = idleTimeout; - } - /** * If true, the HTML response is transformed to XHTML * @ff.default false @@ -1192,14 +613,6 @@ public void setStyleSheetName(String stylesheetName){ this.styleSheetName=stylesheetName; } - /** - * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. - * @ff.default SSL - */ - public void setProtocol(String protocol) { - this.protocol = protocol; - } - /** If set, the status code of the HTTP response is put in the specified sessionKey and the (error or okay) response message is returned. * Setting this property has a side effect. If a 4xx or 5xx result code is returned and if the configuration does not implement * the specific forward for the returned HTTP result code, then the success forward is followed instead of the exception forward. @@ -1207,4 +620,4 @@ public void setProtocol(String protocol) { public void setResultStatusCodeSessionKey(String resultStatusCodeSessionKey) { this.resultStatusCodeSessionKey = resultStatusCodeSessionKey; } -} \ No newline at end of file +} diff --git a/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig b/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig index 9e2a796..f36907c 100644 --- a/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig +++ b/java/nl/nn/adapterframework/http/HttpSenderBase.java-orig @@ -21,81 +21,46 @@ import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerConfigurationException; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.MethodNotSupportedException; import org.apache.http.StatusLine; -import org.apache.http.auth.AuthProtocolState; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.AuthSchemes; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import lombok.Getter; +import lombok.Setter; import nl.nn.adapterframework.configuration.ConfigurationException; import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.core.CanUseSharedResource; import nl.nn.adapterframework.core.HasPhysicalDestination; +import nl.nn.adapterframework.core.ISenderWithParameters; import nl.nn.adapterframework.core.ParameterException; import nl.nn.adapterframework.core.PipeLineSession; import nl.nn.adapterframework.core.Resource; import nl.nn.adapterframework.core.SenderException; import nl.nn.adapterframework.core.SenderResult; import nl.nn.adapterframework.core.TimeoutException; -import nl.nn.adapterframework.encryption.AuthSSLContextFactory; -import nl.nn.adapterframework.encryption.HasKeystore; -import nl.nn.adapterframework.encryption.HasTruststore; import nl.nn.adapterframework.encryption.KeystoreType; -import nl.nn.adapterframework.http.authentication.AuthenticationScheme; -import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; -import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; -import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; -import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; import nl.nn.adapterframework.parameters.Parameter; +import nl.nn.adapterframework.parameters.ParameterList; import nl.nn.adapterframework.parameters.ParameterValue; import nl.nn.adapterframework.parameters.ParameterValueList; -import nl.nn.adapterframework.senders.SenderWithParametersBase; import nl.nn.adapterframework.stream.Message; import nl.nn.adapterframework.task.TimeoutGuard; import nl.nn.adapterframework.util.AppConstants; import nl.nn.adapterframework.util.ClassUtils; -import nl.nn.adapterframework.util.CredentialFactory; import nl.nn.adapterframework.util.StreamUtil; import nl.nn.adapterframework.util.TransformerPool; import nl.nn.adapterframework.util.XmlUtils; @@ -114,55 +79,6 @@ import nl.nn.adapterframework.util.XmlUtils; * another_param_name=another_param_value * * - *

- * Note 1: - * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like - * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. - * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') - *

- * Replace in the directory java\jre\lib\security the following files: - *
    - *
  • local_policy.jar
  • - *
  • US_export_policy.jar
  • - *
- *

- * Note 2: - * To debug ssl-related problems, set the following system property: - *

    - *
  • IBM / WebSphere: -Djavax.net.debug=true
  • - *
  • SUN: -Djavax.net.debug=all
  • - *
- *

- *

- * Note 3: - * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, - * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: - *

    - *
  • open the URL you are trying to reach in InternetExplorer
  • - *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • - *
  • click on tab 'Certificeringspad'
  • - *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • - *
  • click on tab 'Details'
  • - *
  • click on 'Kopieren naar bestand'
  • - *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • - *
  • click 'next', choose a filename
  • - *
  • click 'next' and 'finish'
  • - *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • - *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • - *
  • add the generated certificate (Toevoegen...)
  • - *
  • store the key-database in JKS format
  • - *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • - *
  • use jks for the truststoreType-attribute
  • - *
  • restart your application
  • - *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: - * keytool -import -alias yourAlias -file pathToSavedCertificate
  • - *
- *

- * Note 4: - * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, - * please check password or authAlias configuration of the corresponding certificate. - *

- * * @ff.parameters Any parameters present are appended to the request (when method is GET as request-parameters, when method POST as body part) except the headersParams list, which are added as HTTP headers, and the urlParam header * @ff.forward "<statusCode of the HTTP response>" default * @@ -171,15 +87,17 @@ import nl.nn.adapterframework.util.XmlUtils; */ //TODO: Fix javadoc! -public abstract class HttpSenderBase extends SenderWithParametersBase implements HasPhysicalDestination, HasKeystore, HasTruststore { +public abstract class HttpSenderBase extends HttpSessionBase implements HasPhysicalDestination, ISenderWithParameters, CanUseSharedResource { - private final String CONTEXT_KEY_STATUS_CODE="Http.StatusCode"; - private final String CONTEXT_KEY_REASON_PHRASE="Http.ReasonPhrase"; + private static final String CONTEXT_KEY_STATUS_CODE = "Http.StatusCode"; + private static final String CONTEXT_KEY_REASON_PHRASE = "Http.ReasonPhrase"; public static final String MESSAGE_ID_HEADER = "Message-Id"; public static final String CORRELATION_ID_HEADER = "Correlation-Id"; private final @Getter(onMethod = @__(@Override)) String domain = "Http"; + private @Setter String sharedResourceRef; + private @Getter String url; private @Getter String urlParam = "url"; @@ -192,121 +110,53 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements private @Getter ContentType fullContentType = null; private @Getter String contentType = null; - /* CONNECTION POOL */ - private @Getter int timeout = 10000; - private @Getter int maxConnections = 10; - private @Getter int maxExecuteRetries = 1; - private @Getter boolean staleChecking=true; - private @Getter int staleTimeout = 5000; // [ms] - private @Getter int connectionTimeToLive = 900; // [s] - private @Getter int connectionIdleTimeout = 10; // [s] - private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - private @Getter HttpClientContext httpClientContext = HttpClientContext.create(); - private @Getter CloseableHttpClient httpClient; - - /* SECURITY */ - private @Getter String authAlias; - private @Getter String username; - private @Getter String password; - private @Getter String authDomain; - private @Getter String tokenEndpoint; - private @Getter int tokenExpiry=-1; - private @Getter String clientAuthAlias; - private @Getter String clientId; - private @Getter String clientSecret; - private @Getter String scope; - private @Getter boolean authenticatedTokenRequest; - - /* PROXY */ - private @Getter String proxyHost; - private @Getter int proxyPort=80; - private @Getter String proxyAuthAlias; - private @Getter String proxyUsername; - private @Getter String proxyPassword; - private @Getter String proxyRealm=null; - - /* SSL */ - private @Getter String keystore; - private @Getter String keystoreAuthAlias; - private @Getter String keystorePassword; - private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; - private @Getter String keystoreAlias; - private @Getter String keystoreAliasAuthAlias; - private @Getter String keystoreAliasPassword; - private @Getter String keyManagerAlgorithm=null; - - private @Getter String truststore=null; - private @Getter String truststoreAuthAlias; - private @Getter String truststorePassword=null; - private @Getter KeystoreType truststoreType=KeystoreType.JKS; - private @Getter String trustManagerAlgorithm=null; - private @Getter boolean allowSelfSignedCertificates = false; - private @Getter boolean verifyHostname=true; - private @Getter boolean ignoreCertificateExpiredException=false; - private @Getter String headersParams=""; - private @Getter boolean followRedirects=true; - private @Getter boolean ignoreRedirects=false; private @Getter boolean xhtml=false; private @Getter String styleSheetName=null; - private @Getter String protocol=null; private @Getter String resultStatusCodeSessionKey; private @Getter String parametersToSkipWhenEmpty; private final boolean APPEND_MESSAGEID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.messageid", true); private final boolean APPEND_CORRELATIONID_HEADER = AppConstants.getInstance(getConfigurationClassLoader()).getBoolean("http.headers.correlationid", true); - private boolean disableCookies = false; private TransformerPool transformerPool=null; protected Parameter urlParameter; protected URI staticUri; - private CredentialFactory credentials; - private CredentialFactory user_cf; - private CredentialFactory client_cf; protected Set requestOrBodyParamsSet=new HashSet<>(); protected Set headerParamsSet=new LinkedHashSet<>(); protected Set parametersToSkipWhenEmptySet=new HashSet<>(); - /** - * Makes sure only http(s) requests can be performed. - */ - protected URI getURI(String url) throws URISyntaxException { - URIBuilder uri = new URIBuilder(url); - - if(uri.getScheme() == null) { - throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); - } - if (!uri.getScheme().matches("(?i)https?")) { - throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); - } + protected ParameterList paramList = null; - if (uri.getPath()==null) { - uri.setPath("/"); + @Override + public void addParameter(Parameter p) { + if (paramList==null) { + paramList=new ParameterList(); } + paramList.add(p); + } - log.info(getLogPrefix()+"created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); - return uri.build(); + /** + * return the Parameters + */ + @Override + public ParameterList getParameterList() { + return paramList; } @Override public void configure() throws ConfigurationException { - super.configure(); - - /** - * TODO find out if this really breaks proxy authentication or not. - */ -// httpClientBuilder.disableAuthCaching(); - - Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setConnectTimeout(getTimeout()); - requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); - requestConfigBuilder.setSocketTimeout(getTimeout()); + if(StringUtils.isBlank(sharedResourceRef)) { + log.info("configuring local HttpSession"); + super.configure(); + } if (paramList!=null) { paramList.configure(); + if (StringUtils.isNotEmpty(getHeadersParams())) { StringTokenizer st = new StringTokenizer(getHeadersParams(), ","); while (st.hasMoreElements()) { @@ -348,10 +198,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements } } - if (getMaxConnections() <= 0) { - throw new ConfigurationException(getLogPrefix()+"maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); - } - try { if (urlParameter == null) { if (StringUtils.isEmpty(getUrl())) { @@ -360,102 +206,32 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements staticUri = getURI(getUrl()); } } catch (URISyntaxException e) { - throw new ConfigurationException(getLogPrefix()+"cannot interpret url ["+getUrl()+"]", e); - } - - AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); - - if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { - user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); - credentials = user_cf; - } - client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); - if (credentials==null) { - credentials = client_cf; - } - if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { - throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); - } - HttpHost proxy = null; - CredentialFactory pcf = null; - if (StringUtils.isNotEmpty(getProxyHost())) { - proxy = new HttpHost(getProxyHost(), getProxyPort()); - pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); - requestConfigBuilder.setProxy(proxy); - httpClientBuilder.setProxy(proxy); - } - - try { - setupAuthentication(pcf, proxy, requestConfigBuilder); - } catch (HttpAuthenticationException e) { - throw new ConfigurationException("exception configuring authentication", e); + throw new ConfigurationException("cannot interpret url ["+getUrl()+"]", e); } if (StringUtils.isNotEmpty(getStyleSheetName())) { try { Resource stylesheet = Resource.getResource(this, getStyleSheetName()); if (stylesheet == null) { - throw new ConfigurationException(getLogPrefix() + "cannot find stylesheet ["+getStyleSheetName()+"]"); + throw new ConfigurationException("cannot find stylesheet ["+getStyleSheetName()+"]"); } transformerPool = TransformerPool.getInstance(stylesheet); } catch (IOException e) { - throw new ConfigurationException(getLogPrefix() + "cannot retrieve ["+ getStyleSheetName() + "]", e); + throw new ConfigurationException("cannot retrieve ["+ getStyleSheetName() + "]", e); } catch (TransformerConfigurationException te) { - throw new ConfigurationException(getLogPrefix() + "got error creating transformer from file [" + getStyleSheetName() + "]", te); + throw new ConfigurationException("got error creating transformer from file [" + getStyleSheetName() + "]", te); } } - - httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); - - httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); - - if(areCookiesDisabled()) { - httpClientBuilder.disableCookieManagement(); - } - - // The redirect strategy used to only redirect GET, DELETE and HEAD. - httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() { - @Override - protected boolean isRedirectable(String method) { - return isFollowRedirects(); - } - }); } @Override public void open() throws SenderException { - // In order to support multiThreading and connectionPooling - // If a sslSocketFactory has been defined, the connectionManager has to be initialized with the sslSocketFactory - PoolingHttpClientConnectionManager connectionManager; - int timeToLive = getConnectionTimeToLive(); - if (timeToLive<=0) { - timeToLive = -1; - } - SSLConnectionSocketFactory sslSocketFactory = getSSLConnectionSocketFactory(); - if(sslSocketFactory != null) { - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); - connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); - } - else { - connectionManager = new PoolingHttpClientConnectionManager(timeToLive, TimeUnit.SECONDS); - log.debug(getLogPrefix()+"created default PoolingHttpClientConnectionManager"); - } - - connectionManager.setMaxTotal(getMaxConnections()); - connectionManager.setDefaultMaxPerRoute(getMaxConnections()); - - if (isStaleChecking()) { - log.info(getLogPrefix()+"set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); - connectionManager.setValidateAfterInactivity(getStaleTimeout()); + try { + start(); + } catch (Exception e) { + throw new SenderException(getLogPrefix()+"unable to create HttpClient", e); } - httpClientBuilder.setConnectionManager(connectionManager); - httpClientBuilder.evictIdleConnections(getConnectionIdleTimeout(), TimeUnit.SECONDS); - if (transformerPool!=null) { try { transformerPool.open(); @@ -463,116 +239,36 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements throw new SenderException(getLogPrefix()+"cannot start TransformerPool", e); } } - - httpClient = httpClientBuilder.build(); } @Override - public void close() throws SenderException { - try { - //Close the HttpClient and ConnectionManager to release resources and potential open connections - if(httpClient != null) { - httpClient.close(); - } - } catch (IOException e) { - throw new SenderException(e); - } - - if (transformerPool!=null) { - transformerPool.close(); - } - } - - private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, Builder requestConfigBuilder) throws HttpAuthenticationException { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { - - credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); - - AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); - requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); - requestConfigBuilder.setAuthenticationEnabled(true); - - if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { - OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); - httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); - httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); - } - } - if (proxy!=null) { - AuthScope scope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); - - - if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { - Credentials credentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); - credentialsProvider.setCredentials(scope, credentials); - } - //log.trace("setting credentialProvider [" + credentialsProvider.toString() + "]"); - - if(prefillProxyAuthCache()) { - requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); - - AuthCache authCache = httpClientContext.getAuthCache(); - if(authCache == null) - authCache = new BasicAuthCache(); - - authCache.put(proxy, new BasicScheme()); - httpClientContext.setAuthCache(authCache); - } - - } - - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); - } - - private void preAuthenticate() { - if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { - AuthState authState = httpClientContext.getTargetAuthState(); - if (authState==null) { - authState = new AuthState(); - httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); - } - authState.setState(AuthProtocolState.CHALLENGED); - authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); - } + public Class getObjectType() { + return CloseableHttpClient.class; } - private Credentials getCredentials() { - String uname; - if (StringUtils.isNotEmpty(getAuthDomain())) { - uname = getAuthDomain() + "\\" + credentials.getUsername(); + @Override + public void start() { + if(StringUtils.isNotBlank(sharedResourceRef)) { + setHttpClient(getSharedResource(sharedResourceRef)); } else { - uname = credentials.getUsername(); + buildHttpClient(); } - - return new UsernamePasswordCredentials(uname, credentials.getPassword()); - } - - private AuthenticationScheme getPreferredAuthenticationScheme() { - return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; } - protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws SenderException { - SSLConnectionSocketFactory sslSocketFactory; - HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); - - try { - javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); - sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); - } catch (Exception e) { - throw new SenderException("cannot create or initialize SocketFactory", e); + @Override + public void close() { + if(StringUtils.isBlank(sharedResourceRef)) { + super.stop(); } - // This method will be overwritten by the connectionManager when connectionPooling is enabled! - // Can still be null when no default or an invalid system sslSocketFactory has been defined - if(sslSocketFactory != null) { - httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + if (transformerPool!=null) { + transformerPool.close(); } - return sslSocketFactory; } - protected boolean appendParameters(boolean parametersAppended, StringBuffer path, ParameterValueList parameters) throws SenderException { + protected boolean appendParameters(boolean parametersAppended, StringBuilder path, ParameterValueList parameters) throws SenderException { if (parameters != null) { - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending ["+parameters.size()+"] parameters"); + log.debug("appending [{}] parameters", parameters::size); for(ParameterValue pv : parameters) { if (requestOrBodyParamsSet.contains(pv.getName())) { String value = pv.asStringValue(""); @@ -586,7 +282,7 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements } String parameterToAppend = pv.getDefinition().getName() +"="+ URLEncoder.encode(value, getCharSet()); - if (log.isDebugEnabled()) log.debug(getLogPrefix()+"appending parameter ["+parameterToAppend+"]"); + log.debug("appending parameter [{}]", parameterToAppend); path.append(parameterToAppend); } catch (UnsupportedEncodingException e) { throw new SenderException(getLogPrefix()+"["+getCharSet()+"] encoding error. Failed to add parameter ["+pv.getDefinition().getName()+"]", e); @@ -598,6 +294,17 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements return parametersAppended; } + + /** + * Returns the true name of the class and not XsltPipe$$EnhancerBySpringCGLIB$$563e6b5d. + * {@link ClassUtils#nameOf(Object)} makes sure the original class will be used. + * + * @return className + name of the ISender + */ + protected String getLogPrefix() { + return ClassUtils.nameOf(this) + " "; + } + /** * Custom implementation to create a {@link HttpRequestBase HttpRequest} object. * @param uri endpoint to send the message to @@ -690,17 +397,16 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements preAuthenticate(); - log.info(getLogPrefix()+"configured httpclient for host ["+targetUri.getHost()+"]"); + log.info("configured httpclient for host [{}]", targetUri::getHost); } catch (Exception e) { throw new SenderException(e); } - Message result = null; - int statusCode = -1; + Message result; + int statusCode; boolean success; - String reasonPhrase = null; - HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + String reasonPhrase; TimeoutGuard tg = new TimeoutGuard(1+getTimeout()/1000, getName()) { @@ -711,9 +417,9 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements }; try { - log.debug(getLogPrefix()+"executing method [" + httpRequestBase.getRequestLine() + "]"); - HttpResponse httpResponse = getHttpClient().execute(targetHost, httpRequestBase, httpClientContext); - log.debug(getLogPrefix()+"executed method"); + log.debug("executing method [{}]", httpRequestBase::getRequestLine); + HttpResponse httpResponse = execute(targetUri, httpRequestBase); + log.debug("executed method"); HttpResponseHandler responseHandler = new HttpResponseHandler(httpResponse); StatusLine statusline = httpResponse.getStatusLine(); @@ -727,14 +433,14 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements // Only give warnings for 4xx (client errors) and 5xx (server errors) if (statusCode >= 400 && statusCode < 600) { - log.warn(getLogPrefix()+"status ["+statusline.toString()+"]"); + log.warn("status [{}]", statusline); } else { - log.debug(getLogPrefix()+"status ["+statusCode+"]"); + log.debug("status [{}]", statusCode); } result = extractResult(responseHandler, session); - log.debug(getLogPrefix()+"retrieved result ["+result+"]"); + log.debug("retrieved result [{}]", result); } catch (IOException e) { httpRequestBase.abort(); if (e instanceof SocketTimeoutException) { @@ -749,8 +455,8 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements // in a sessionKey for later use in the pipeline. // // IMPORTANT: It is possible that poorly written implementations - // wont read or close the response. - // This will cause the connection to become stale.. + // won't read or close the response. + // This will cause the connection to become stale. if (tg.cancel()) { throw new TimeoutException(getLogPrefix()+"timeout of ["+getTimeout()+"] ms exceeded"); @@ -762,15 +468,16 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements } if (isXhtml() && !Message.isEmpty(result)) { + // TODO: Streaming XHTML conversion for better performance with large result message? String xhtml; - try { - xhtml = XmlUtils.toXhtml(result); + try(Message m = result) { + xhtml = XmlUtils.toXhtml(m); } catch (IOException e) { throw new SenderException("error reading http response as String", e); } if (transformerPool != null && xhtml != null) { - log.debug(getLogPrefix() + " transforming result [" + xhtml + "]"); + log.debug("transforming result [{}]", xhtml); try { xhtml = transformerPool.transform(Message.asSource(xhtml)); } catch (Exception e) { @@ -781,7 +488,7 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements result = Message.asMessage(xhtml); } - if (result==null) { + if (result == null) { result = Message.nullMessage(); } log.debug("Storing [{}]=[{}], [{}]=[{}]", CONTEXT_KEY_STATUS_CODE, statusCode, CONTEXT_KEY_REASON_PHRASE, reasonPhrase); @@ -835,174 +542,18 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements charSet = string; } - /** - * Timeout in ms of obtaining a connection/result. 0 means no timeout - * @ff.default 10000 - */ - public void setTimeout(int i) { - timeout = i; - } - - /** - * The maximum number of concurrent connections - * @ff.default 10 - */ - public void setMaxConnections(int i) { - maxConnections = i; - } - - /** - * The maximum number of times the execution is retried - * @ff.default 1 (for repeatable messages) else 0 - */ - public void setMaxExecuteRetries(int i) { - maxExecuteRetries = i; - } - - /** Authentication alias used for authentication to the host */ - public void setAuthAlias(String string) { - authAlias = string; - } - - /** Username used for authentication to the host */ - public void setUsername(String username) { - this.username = username; - } @Deprecated @ConfigurationWarning("Please use attribute username instead") public void setUserName(String username) { setUsername(username); } - /** Password used for authentication to the host */ - public void setPassword(String string) { - password = string; - } - - /** - * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
- *
- * Assuming the following user:
- * UPN: john.doe@CorpDomain.biz
- * sAMAccountName: CORPDOMAIN\john.doe
- *
- * The username attribute may be set to john.doe
- * The AuthDomain attribute may be set to CORPDOMAIN
- */ - @Deprecated - @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") - public void setAuthDomain(String string) { - authDomain = string; - } - - /** - * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, - * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests - * in an HTTP Header 'Authorization' with a 'Bearer' prefix. - */ - public void setTokenEndpoint(String string) { - tokenEndpoint = string; - } - /** - * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token - * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, - * or when the regular server returns a 401 status with a challenge. - * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. - * @ff.default -1 - */ - public void setTokenExpiry(int value) { - tokenExpiry = value; - } - /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ - public void setClientAlias(String clientAuthAlias) { - this.clientAuthAlias = clientAuthAlias; - } - /** Client_id used in authentication to tokenEndpoint */ - public void setClientId(String clientId) { - this.clientId = clientId; - } - - /** Client_secret used in authentication to tokenEndpoint */ - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ - public void setScope(String string) { - scope = string; - } - /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ - public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { - this.authenticatedTokenRequest = authenticatedTokenRequest; - } - - - /** Proxy host */ - public void setProxyHost(String string) { - proxyHost = string; - } - - /** - * Proxy port - * @ff.default 80 - */ - public void setProxyPort(int i) { - proxyPort = i; - } - - /** Alias used to obtain credentials for authentication to proxy */ - public void setProxyAuthAlias(String string) { - proxyAuthAlias = string; - } - - /** - * Proxy username - * @ff.default - */ - public void setProxyUsername(String string) { - proxyUsername = string; - } @Deprecated @ConfigurationWarning("Please use \"proxyUsername\" instead") public void setProxyUserName(String string) { setProxyUsername(string); } - /** - * Proxy password - * @ff.default - */ - public void setProxyPassword(String string) { - proxyPassword = string; - } - - /** - * Proxy realm - * @ff.default - */ - public void setProxyRealm(String string) { - proxyRealm = StringUtils.isNotEmpty(string) ? string : null; - } - - /** - * TODO: make this configurable - * @return false - */ - public boolean prefillProxyAuthCache() { - return false; - } - - /** - * Disables the use of cookies, making the sender completely stateless - * @ff.default false - */ - public void setDisableCookies(boolean disableCookies) { - this.disableCookies = disableCookies; - } - public boolean areCookiesDisabled() { - return disableCookies; - } - - @Deprecated @ConfigurationWarning("Please use attribute keystore instead") public void setCertificate(String string) { @@ -1024,87 +575,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements setKeystorePassword(string); } - /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ - @Override - public void setKeystore(String string) { - keystore = string; - } - - @Override - public void setKeystoreType(KeystoreType value) { - keystoreType = value; - } - - @Override - public void setKeystoreAuthAlias(String string) { - keystoreAuthAlias = string; - } - - @Override - public void setKeystorePassword(String string) { - keystorePassword = string; - } - - @Override - public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { - this.keyManagerAlgorithm = keyManagerAlgorithm; - } - - @Override - public void setKeystoreAlias(String string) { - keystoreAlias = string; - } - @Override - public void setKeystoreAliasAuthAlias(String string) { - keystoreAliasAuthAlias = string; - } - @Override - public void setKeystoreAliasPassword(String string) { - keystoreAliasPassword = string; - } - - @Override - /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ - public void setTruststore(String string) { - truststore = string; - } - - @Override - public void setTruststoreAuthAlias(String string) { - truststoreAuthAlias = string; - } - - @Override - public void setTruststorePassword(String string) { - truststorePassword = string; - } - - @Override - public void setTruststoreType(KeystoreType value) { - truststoreType = value; - } - - @Override - public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { - this.trustManagerAlgorithm = trustManagerAlgorithm; - } - - @Override - public void setVerifyHostname(boolean b) { - verifyHostname = b; - } - - @Override - public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { - this.allowSelfSignedCertificates = allowSelfSignedCertificates; - } - - @Override - public void setIgnoreCertificateExpiredException(boolean b) { - ignoreCertificateExpiredException = b; - } - - /** Comma separated list of parameter names which should be set as HTTP headers */ public void setHeadersParams(String headersParams) { this.headersParams = headersParams; @@ -1115,56 +585,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements this.parametersToSkipWhenEmpty = parametersToSkipWhenEmpty; } - - /** - * If true, a redirect request will be honoured, e.g. to switch to HTTPS - * @ff.default true - */ - public void setFollowRedirects(boolean b) { - followRedirects = b; - } - - /** - * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful - * @ff.default false - */ - public void setIgnoreRedirects(boolean b) { - ignoreRedirects = b; - } - - - /** - * Controls whether connections checked to be stale, i.e. appear open, but are not. - * @ff.default true - */ - public void setStaleChecking(boolean b) { - staleChecking = b; - } - - /** - * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. - * @ff.default 5000 ms - */ - public void setStaleTimeout(int timeout) { - staleTimeout = timeout; - } - - /** - * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. - * @ff.default 900 s - */ - public void setConnectionTimeToLive(int timeToLive) { - connectionTimeToLive = timeToLive; - } - - /** - * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool - * @ff.default 10 s - */ - public void setConnectionIdleTimeout(int idleTimeout) { - connectionIdleTimeout = idleTimeout; - } - /** * If true, the HTML response is transformed to XHTML * @ff.default false @@ -1178,14 +598,6 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements this.styleSheetName=stylesheetName; } - /** - * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. - * @ff.default SSL - */ - public void setProtocol(String protocol) { - this.protocol = protocol; - } - /** If set, the status code of the HTTP response is put in the specified sessionKey and the (error or okay) response message is returned. * Setting this property has a side effect. If a 4xx or 5xx result code is returned and if the configuration does not implement * the specific forward for the returned HTTP result code, then the success forward is followed instead of the exception forward. @@ -1193,4 +605,4 @@ public abstract class HttpSenderBase extends SenderWithParametersBase implements public void setResultStatusCodeSessionKey(String resultStatusCodeSessionKey) { this.resultStatusCodeSessionKey = resultStatusCodeSessionKey; } -} \ No newline at end of file +} diff --git a/java/nl/nn/adapterframework/http/HttpSessionBase.java b/java/nl/nn/adapterframework/http/HttpSessionBase.java new file mode 100644 index 0000000..699a545 --- /dev/null +++ b/java/nl/nn/adapterframework/http/HttpSessionBase.java @@ -0,0 +1,748 @@ +/* + Copyright 2017-2023 WeAreFrank! + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +package nl.nn.adapterframework.http; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; +import javax.net.ssl.HostnameVerifier; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthProtocolState; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.ApplicationContext; + +import lombok.Getter; +import lombok.Setter; +import nl.nn.adapterframework.configuration.ConfigurationException; +import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.encryption.AuthSSLContextFactory; +import nl.nn.adapterframework.encryption.HasKeystore; +import nl.nn.adapterframework.encryption.HasTruststore; +import nl.nn.adapterframework.encryption.KeystoreType; +import nl.nn.adapterframework.http.authentication.AuthenticationScheme; +import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; +import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; +import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; +import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; +import nl.nn.adapterframework.lifecycle.ConfigurableLifecycle; +import nl.nn.adapterframework.util.ClassUtils; +import nl.nn.adapterframework.util.CredentialFactory; +import nl.nn.adapterframework.util.LogUtil; + +/** + *

+ * Note 1: + * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like + * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. + * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') + *

+ * Replace in the directory java\jre\lib\security the following files: + *
    + *
  • local_policy.jar
  • + *
  • US_export_policy.jar
  • + *
+ *

+ * Note 2: + * To debug ssl-related problems, set the following system property: + *

    + *
  • IBM / WebSphere: -Djavax.net.debug=true
  • + *
  • SUN: -Djavax.net.debug=all
  • + *
+ *

+ *

+ * Note 3: + * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, + * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: + *

    + *
  • open the URL you are trying to reach in InternetExplorer
  • + *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • + *
  • click on tab 'Certificeringspad'
  • + *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • + *
  • click on tab 'Details'
  • + *
  • click on 'Kopieren naar bestand'
  • + *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • + *
  • click 'next', choose a filename
  • + *
  • click 'next' and 'finish'
  • + *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • + *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • + *
  • add the generated certificate (Toevoegen...)
  • + *
  • store the key-database in JKS format
  • + *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • + *
  • use jks for the truststoreType-attribute
  • + *
  • restart your application
  • + *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: + * keytool -import -alias yourAlias -file pathToSavedCertificate
  • + *
+ *

+ * Note 4: + * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, + * please check password or authAlias configuration of the corresponding certificate. + *

+ * + * @author Niels Meijer + * @since 7.0 + */ +public abstract class HttpSessionBase implements ConfigurableLifecycle, HasKeystore, HasTruststore { + protected Logger log = LogUtil.getLogger(this); + + private @Getter ClassLoader configurationClassLoader = Thread.currentThread().getContextClassLoader(); + private @Getter @Setter String name; + private @Getter @Setter ApplicationContext applicationContext; + + /* CONNECTION POOL */ + private @Getter int timeout = 10000; + private @Getter int maxConnections = 10; + private @Getter int maxExecuteRetries = 1; + private @Getter boolean staleChecking=true; + private @Getter int staleTimeout = 5000; // [ms] + private @Getter int connectionTimeToLive = 900; // [s] + private @Getter int connectionIdleTimeout = 10; // [s] + private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + private HttpClientContext httpClientContext = HttpClientContext.create(); + private @Getter CloseableHttpClient httpClient; + + /* SECURITY */ + private @Getter String authAlias; + private @Getter String username; + private @Getter String password; + private @Getter String authDomain; + private @Getter String tokenEndpoint; + private @Getter int tokenExpiry=-1; + private @Getter String clientAuthAlias; + private @Getter String clientId; + private @Getter String clientSecret; + private @Getter String scope; + private @Getter boolean authenticatedTokenRequest; + + /* PROXY */ + private @Getter String proxyHost; + private @Getter int proxyPort=80; + private @Getter String proxyAuthAlias; + private @Getter String proxyUsername; + private @Getter String proxyPassword; + private @Getter String proxyRealm=null; + private @Getter boolean prefillProxyAuthCache; + + /* SSL */ + private @Getter String keystore; + private @Getter String keystoreAuthAlias; + private @Getter String keystorePassword; + private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; + private @Getter String keystoreAlias; + private @Getter String keystoreAliasAuthAlias; + private @Getter String keystoreAliasPassword; + private @Getter String keyManagerAlgorithm=null; + + private @Getter String truststore=null; + private @Getter String truststoreAuthAlias; + private @Getter String truststorePassword=null; + private @Getter KeystoreType truststoreType=KeystoreType.JKS; + private @Getter String trustManagerAlgorithm=null; + private @Getter boolean allowSelfSignedCertificates = false; + private @Getter boolean verifyHostname=true; + private @Getter boolean ignoreCertificateExpiredException=false; + + private @Getter boolean followRedirects=true; + private @Getter boolean ignoreRedirects=false; + private @Getter String protocol=null; + private SSLConnectionSocketFactory sslSocketFactory; + + private boolean disableCookies = false; + + private CredentialFactory credentials; + private CredentialFactory user_cf; + private CredentialFactory client_cf; + + /** + * Makes sure only http(s) requests can be performed. + */ + protected URI getURI(String url) throws URISyntaxException { + URIBuilder uri = new URIBuilder(url); + + if(uri.getScheme() == null) { + throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); + } + if (!uri.getScheme().matches("(?i)https?")) { + throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); + } + + if (uri.getPath()==null) { + uri.setPath("/"); + } + + log.info("created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); + return uri.build(); + } + + @Override + public void configure() throws ConfigurationException { + /** + * TODO find out if this really breaks proxy authentication or not. + */ +// httpClientBuilder.disableAuthCaching(); + + if (getMaxConnections() <= 0) { + throw new ConfigurationException("maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); + } + + AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); + + if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { + user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); + credentials = user_cf; + } + client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); + if (credentials==null) { + credentials = client_cf; + } + if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { + throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); + } + + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectTimeout(getTimeout()); + requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); + requestConfigBuilder.setSocketTimeout(getTimeout()); + + HttpHost proxy = null; + CredentialFactory pcf = null; + if (StringUtils.isNotEmpty(getProxyHost())) { + proxy = new HttpHost(getProxyHost(), getProxyPort()); + pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); + requestConfigBuilder.setProxy(proxy); + httpClientBuilder.setProxy(proxy); + } + + try { + setupAuthentication(pcf, proxy, requestConfigBuilder); + } catch (HttpAuthenticationException e) { + throw new ConfigurationException("exception configuring authentication", e); + } + + httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); + + if(areCookiesDisabled()) { + httpClientBuilder.disableCookieManagement(); + } + httpClientBuilder.evictIdleConnections((long) getConnectionIdleTimeout(), TimeUnit.SECONDS); + + sslSocketFactory = getSSLConnectionSocketFactory(); //Configure it here, so we can handle exceptions + + configureRedirectStrategy(); + } + + /** The redirect strategy used to only redirect GET, DELETE and HEAD. */ + private void configureRedirectStrategy() { + if(isFollowRedirects()) { + httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy(new String[] { HttpGet.METHOD_NAME, HttpHead.METHOD_NAME, HttpDelete.METHOD_NAME })); + } else { + httpClientBuilder.disableRedirectHandling(); + } + } + + /** + * In order to support multiThreading and connectionPooling. + * The connectionManager has to be initialized with a sslSocketFactory. + * The pool must be re-created once closed. + */ + public void configureConnectionManager() { + int timeToLive = getConnectionTimeToLive(); + if (timeToLive<=0) { + timeToLive = -1; + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); + log.debug("created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); + + connectionManager.setMaxTotal(getMaxConnections()); + connectionManager.setDefaultMaxPerRoute(getMaxConnections()); + + if (isStaleChecking()) { + log.info("set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); + connectionManager.setValidateAfterInactivity(getStaleTimeout()); + } + + httpClientBuilder.setConnectionManager(connectionManager); + } + + @Override + public void start() { + buildHttpClient(); + } + + protected void buildHttpClient() { + configureConnectionManager(); + httpClient = httpClientBuilder.build(); + } + + protected void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public boolean isRunning() { + return getHttpClient() != null; + } + + @Override + public void stop() { + //Close the HttpClient and ConnectionManager to release resources and potential open connections + if(httpClient != null) { + try { + httpClient.close(); + } catch (IOException e) { + log.warn("unable to close HttpClient", e); + } + } + } + + private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, RequestConfig.Builder requestConfigBuilder) throws HttpAuthenticationException { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { + + credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); + + AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); + requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); + requestConfigBuilder.setAuthenticationEnabled(true); + + if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { + OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); + httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); + httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); + } + } + if (proxy!=null) { + AuthScope authScope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); + + + if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { + Credentials httpCredentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); + credentialsProvider.setCredentials(authScope, httpCredentials); + } + log.trace("setting credentialProvider [{}]", credentialsProvider); + + if(isPrefillProxyAuthCache()) { + requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); + + AuthCache authCache = httpClientContext.getAuthCache(); + if(authCache == null) + authCache = new BasicAuthCache(); + + authCache.put(proxy, new BasicScheme()); + httpClientContext.setAuthCache(authCache); + } + + } + + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + + protected void preAuthenticate() { + if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { + AuthState authState = httpClientContext.getTargetAuthState(); + if (authState==null) { + authState = new AuthState(); + httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); + } + authState.setState(AuthProtocolState.CHALLENGED); + authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); + } + } + + private Credentials getCredentials() { + String uname; + if (StringUtils.isNotEmpty(getAuthDomain())) { + uname = getAuthDomain() + "\\" + credentials.getUsername(); + } else { + uname = credentials.getUsername(); + } + + return new UsernamePasswordCredentials(uname, credentials.getPassword()); + } + + private AuthenticationScheme getPreferredAuthenticationScheme() { + return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; + } + + @Nonnull + protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws ConfigurationException { + SSLConnectionSocketFactory sslSocketFactory; + HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); + + try { + javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); + sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); + } catch (Exception e) { + throw new ConfigurationException("cannot create or initialize SocketFactory", e); + } + + // This method will be overwritten by the connectionManager when connectionPooling is enabled! + // Can still be null when no default or an invalid system sslSocketFactory has been defined + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + return sslSocketFactory; + } + + protected HttpResponse execute(URI targetUri, HttpRequestBase httpRequestBase) throws IOException { + return execute(null, targetUri, httpRequestBase); + } + + protected HttpResponse execute(CloseableHttpClient httpClient, URI targetUri, HttpRequestBase httpRequestBase) throws IOException { + if (httpClient == null) { + httpClient = getHttpClient(); + } + HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + return httpClient.execute(targetHost, httpRequestBase, httpClientContext); + } + + /** + * Timeout in ms of obtaining a connection/result. 0 means no timeout + * @ff.default 10000 + */ + public void setTimeout(int i) { + timeout = i; + } + + /** + * The maximum number of concurrent connections + * @ff.default 10 + */ + public void setMaxConnections(int i) { + maxConnections = i; + } + + /** + * The maximum number of times the execution is retried + * @ff.default 1 (for repeatable messages) else 0 + */ + public void setMaxExecuteRetries(int i) { + maxExecuteRetries = i; + } + + /** Authentication alias used for authentication to the host */ + public void setAuthAlias(String string) { + authAlias = string; + } + + /** Username used for authentication to the host */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
+ *
+ * Assuming the following user:
+ * UPN: john.doe@CorpDomain.biz
+ * sAMAccountName: CORPDOMAIN\john.doe
+ *
+ * The username attribute may be set to john.doe
+ * The AuthDomain attribute may be set to CORPDOMAIN
+ */ + @Deprecated + @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") + public void setAuthDomain(String string) { + authDomain = string; + } + + /** Password used for authentication to the host */ + public void setPassword(String string) { + password = string; + } + + /** + * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, + * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests + * in an HTTP Header 'Authorization' with a 'Bearer' prefix. + */ + public void setTokenEndpoint(String string) { + tokenEndpoint = string; + } + /** + * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token + * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, + * or when the regular server returns a 401 status with a challenge. + * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. + * @ff.default -1 + */ + public void setTokenExpiry(int value) { + tokenExpiry = value; + } + /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ + public void setClientAlias(String clientAuthAlias) { + this.clientAuthAlias = clientAuthAlias; + } + /** Client_id used in authentication to tokenEndpoint */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** Client_secret used in authentication to tokenEndpoint */ + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ + public void setScope(String string) { + scope = string; + } + /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ + public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { + this.authenticatedTokenRequest = authenticatedTokenRequest; + } + + + /** Proxy host */ + public void setProxyHost(String string) { + proxyHost = string; + } + + /** + * Proxy port + * @ff.default 80 + */ + public void setProxyPort(int i) { + proxyPort = i; + } + + /** Alias used to obtain credentials for authentication to proxy */ + public void setProxyAuthAlias(String string) { + proxyAuthAlias = string; + } + + /** + * Proxy username + * @ff.default + */ + public void setProxyUsername(String string) { + proxyUsername = string; + } + + /** + * Proxy password + * @ff.default + */ + public void setProxyPassword(String string) { + proxyPassword = string; + } + + /** + * Proxy realm + * @ff.default + */ + public void setProxyRealm(String string) { + proxyRealm = StringUtils.isNotEmpty(string) ? string : null; + } + + /** + * Create a pre-emptive login context for the proxy connection(s). + */ + public void setPrefillProxyAuthCache(boolean b) { + this.prefillProxyAuthCache = b; + } + + /** + * Disables the use of cookies, making the sender completely stateless + * @ff.default false + */ + public void setDisableCookies(boolean disableCookies) { + this.disableCookies = disableCookies; + } + public boolean areCookiesDisabled() { + return disableCookies; + } + + + /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ + @Override + public void setKeystore(String string) { + keystore = string; + } + + @Override + public void setKeystoreType(KeystoreType value) { + keystoreType = value; + } + + @Override + public void setKeystoreAuthAlias(String string) { + keystoreAuthAlias = string; + } + + @Override + public void setKeystorePassword(String string) { + keystorePassword = string; + } + + @Override + public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { + this.keyManagerAlgorithm = keyManagerAlgorithm; + } + + @Override + public void setKeystoreAlias(String string) { + keystoreAlias = string; + } + @Override + public void setKeystoreAliasAuthAlias(String string) { + keystoreAliasAuthAlias = string; + } + @Override + public void setKeystoreAliasPassword(String string) { + keystoreAliasPassword = string; + } + + @Override + /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ + public void setTruststore(String string) { + truststore = string; + } + + @Override + public void setTruststoreAuthAlias(String string) { + truststoreAuthAlias = string; + } + + @Override + public void setTruststorePassword(String string) { + truststorePassword = string; + } + + @Override + public void setTruststoreType(KeystoreType value) { + truststoreType = value; + } + + @Override + public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { + this.trustManagerAlgorithm = trustManagerAlgorithm; + } + + @Override + public void setVerifyHostname(boolean b) { + verifyHostname = b; + } + + @Override + public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { + this.allowSelfSignedCertificates = allowSelfSignedCertificates; + } + + @Override + public void setIgnoreCertificateExpiredException(boolean b) { + ignoreCertificateExpiredException = b; + } + + /** + * If true, a redirect request will be honoured, e.g. to switch to HTTPS + * @ff.default true + */ + public void setFollowRedirects(boolean b) { + followRedirects = b; + } + + /** + * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful + * @ff.default false + */ + public void setIgnoreRedirects(boolean b) { + ignoreRedirects = b; + } + + + /** + * Controls whether connections checked to be stale, i.e. appear open, but are not. + * @ff.default true + */ + public void setStaleChecking(boolean b) { + staleChecking = b; + } + + /** + * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. + * @ff.default 5000 ms + */ + public void setStaleTimeout(int timeout) { + staleTimeout = timeout; + } + + /** + * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. + * @ff.default 900 s + */ + public void setConnectionTimeToLive(int timeToLive) { + connectionTimeToLive = timeToLive; + } + + /** + * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool + * @ff.default 10 s + */ + public void setConnectionIdleTimeout(int idleTimeout) { + connectionIdleTimeout = idleTimeout; + } + + /** + * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. + * @ff.default SSL + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } +} diff --git a/java/nl/nn/adapterframework/http/HttpSessionBase.java-orig b/java/nl/nn/adapterframework/http/HttpSessionBase.java-orig new file mode 100644 index 0000000..dcb2091 --- /dev/null +++ b/java/nl/nn/adapterframework/http/HttpSessionBase.java-orig @@ -0,0 +1,741 @@ +/* + Copyright 2017-2023 WeAreFrank! + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +package nl.nn.adapterframework.http; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; +import javax.net.ssl.HostnameVerifier; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthProtocolState; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.ApplicationContext; + +import lombok.Getter; +import lombok.Setter; +import nl.nn.adapterframework.configuration.ConfigurationException; +import nl.nn.adapterframework.configuration.ConfigurationWarning; +import nl.nn.adapterframework.encryption.AuthSSLContextFactory; +import nl.nn.adapterframework.encryption.HasKeystore; +import nl.nn.adapterframework.encryption.HasTruststore; +import nl.nn.adapterframework.encryption.KeystoreType; +import nl.nn.adapterframework.http.authentication.AuthenticationScheme; +import nl.nn.adapterframework.http.authentication.HttpAuthenticationException; +import nl.nn.adapterframework.http.authentication.OAuthAccessTokenManager; +import nl.nn.adapterframework.http.authentication.OAuthAuthenticationScheme; +import nl.nn.adapterframework.http.authentication.OAuthPreferringAuthenticationStrategy; +import nl.nn.adapterframework.lifecycle.ConfigurableLifecycle; +import nl.nn.adapterframework.util.ClassUtils; +import nl.nn.adapterframework.util.CredentialFactory; +import nl.nn.adapterframework.util.LogUtil; + +/** + *

+ * Note 1: + * Some certificates require the <java_home>/jre/lib/security/xxx_policy.jar files to be upgraded to unlimited strength. Typically, in such a case, an error message like + * Error in loading the keystore: Private key decryption error: (java.lang.SecurityException: Unsupported keysize or algorithm parameters is observed. + * For IBM JDKs these files can be downloaded from http://www.ibm.com/developerworks/java/jdk/security/50/ (scroll down to 'IBM SDK Policy files') + *

+ * Replace in the directory java\jre\lib\security the following files: + *
    + *
  • local_policy.jar
  • + *
  • US_export_policy.jar
  • + *
+ *

+ * Note 2: + * To debug ssl-related problems, set the following system property: + *

    + *
  • IBM / WebSphere: -Djavax.net.debug=true
  • + *
  • SUN: -Djavax.net.debug=all
  • + *
+ *

+ *

+ * Note 3: + * In case javax.net.ssl.SSLHandshakeException: unknown certificate-exceptions are thrown, + * probably the certificate of the other party is not trusted. Try to use one of the certificates in the path as your truststore by doing the following: + *

    + *
  • open the URL you are trying to reach in InternetExplorer
  • + *
  • click on the yellow padlock on the right in the bottom-bar. This opens the certificate information window
  • + *
  • click on tab 'Certificeringspad'
  • + *
  • double click on root certificate in the tree displayed. This opens the certificate information window for the root certificate
  • + *
  • click on tab 'Details'
  • + *
  • click on 'Kopieren naar bestand'
  • + *
  • click 'next', choose 'DER Encoded Binary X.509 (.CER)'
  • + *
  • click 'next', choose a filename
  • + *
  • click 'next' and 'finish'
  • + *
  • Start IBM key management tool ikeyman.bat, located in Program Files/IBM/WebSphere Studio/Application Developer/v5.1.2/runtimes/base_v51/bin (or similar)
  • + *
  • create a new key-database (Sleuteldatabase -> Nieuw...), or open the default key.jks (default password="changeit")
  • + *
  • add the generated certificate (Toevoegen...)
  • + *
  • store the key-database in JKS format
  • + *
  • if you didn't use the standard keydatabase, then reference the file in the truststore-attribute in Configuration.xml (include the file as a resource)
  • + *
  • use jks for the truststoreType-attribute
  • + *
  • restart your application
  • + *
  • instead of IBM ikeyman you can use the standard java tool keytool as follows: + * keytool -import -alias yourAlias -file pathToSavedCertificate
  • + *
+ *

+ * Note 4: + * In case cannot create or initialize SocketFactory: (IOException) Unable to verify MAC-exceptions are thrown, + * please check password or authAlias configuration of the corresponding certificate. + *

+ * + * @author Niels Meijer + * @since 7.0 + */ +public abstract class HttpSessionBase implements ConfigurableLifecycle, HasKeystore, HasTruststore { + protected Logger log = LogUtil.getLogger(this); + + private @Getter ClassLoader configurationClassLoader = Thread.currentThread().getContextClassLoader(); + private @Getter @Setter String name; + private @Getter @Setter ApplicationContext applicationContext; + + /* CONNECTION POOL */ + private @Getter int timeout = 10000; + private @Getter int maxConnections = 10; + private @Getter int maxExecuteRetries = 1; + private @Getter boolean staleChecking=true; + private @Getter int staleTimeout = 5000; // [ms] + private @Getter int connectionTimeToLive = 900; // [s] + private @Getter int connectionIdleTimeout = 10; // [s] + private HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + private HttpClientContext httpClientContext = HttpClientContext.create(); + private @Getter CloseableHttpClient httpClient; + + /* SECURITY */ + private @Getter String authAlias; + private @Getter String username; + private @Getter String password; + private @Getter String authDomain; + private @Getter String tokenEndpoint; + private @Getter int tokenExpiry=-1; + private @Getter String clientAuthAlias; + private @Getter String clientId; + private @Getter String clientSecret; + private @Getter String scope; + private @Getter boolean authenticatedTokenRequest; + + /* PROXY */ + private @Getter String proxyHost; + private @Getter int proxyPort=80; + private @Getter String proxyAuthAlias; + private @Getter String proxyUsername; + private @Getter String proxyPassword; + private @Getter String proxyRealm=null; + private @Getter boolean prefillProxyAuthCache; + + /* SSL */ + private @Getter String keystore; + private @Getter String keystoreAuthAlias; + private @Getter String keystorePassword; + private @Getter KeystoreType keystoreType=KeystoreType.PKCS12; + private @Getter String keystoreAlias; + private @Getter String keystoreAliasAuthAlias; + private @Getter String keystoreAliasPassword; + private @Getter String keyManagerAlgorithm=null; + + private @Getter String truststore=null; + private @Getter String truststoreAuthAlias; + private @Getter String truststorePassword=null; + private @Getter KeystoreType truststoreType=KeystoreType.JKS; + private @Getter String trustManagerAlgorithm=null; + private @Getter boolean allowSelfSignedCertificates = false; + private @Getter boolean verifyHostname=true; + private @Getter boolean ignoreCertificateExpiredException=false; + + private @Getter boolean followRedirects=true; + private @Getter boolean ignoreRedirects=false; + private @Getter String protocol=null; + private SSLConnectionSocketFactory sslSocketFactory; + + private boolean disableCookies = false; + + private CredentialFactory credentials; + private CredentialFactory user_cf; + private CredentialFactory client_cf; + + /** + * Makes sure only http(s) requests can be performed. + */ + protected URI getURI(String url) throws URISyntaxException { + URIBuilder uri = new URIBuilder(url); + + if(uri.getScheme() == null) { + throw new URISyntaxException("", "must use an absolute url starting with http(s)://"); + } + if (!uri.getScheme().matches("(?i)https?")) { + throw new IllegalArgumentException(ClassUtils.nameOf(this) + " only supports web based schemes. (http or https)"); + } + + if (uri.getPath()==null) { + uri.setPath("/"); + } + + log.info("created uri: scheme=["+uri.getScheme()+"] host=["+uri.getHost()+"] path=["+uri.getPath()+"]"); + return uri.build(); + } + + @Override + public void configure() throws ConfigurationException { + /** + * TODO find out if this really breaks proxy authentication or not. + */ +// httpClientBuilder.disableAuthCaching(); + + if (getMaxConnections() <= 0) { + throw new ConfigurationException("maxConnections is set to ["+getMaxConnections()+"], which is not enough for adequate operation"); + } + + AuthSSLContextFactory.verifyKeystoreConfiguration(this, this); + + if (StringUtils.isNotEmpty(getAuthAlias()) || StringUtils.isNotEmpty(getUsername())) { + user_cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword()); + credentials = user_cf; + } + client_cf = new CredentialFactory(getClientAuthAlias(), getClientId(), getClientSecret()); + if (credentials==null) { + credentials = client_cf; + } + if (StringUtils.isNotEmpty(getTokenEndpoint()) && StringUtils.isEmpty(getClientAuthAlias()) && StringUtils.isEmpty(getClientId())) { + throw new ConfigurationException("To obtain accessToken at tokenEndpoint ["+getTokenEndpoint()+"] a clientAuthAlias or ClientId and ClientSecret must be specified"); + } + + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectTimeout(getTimeout()); + requestConfigBuilder.setConnectionRequestTimeout(getTimeout()); + requestConfigBuilder.setSocketTimeout(getTimeout()); + + HttpHost proxy = null; + CredentialFactory pcf = null; + if (StringUtils.isNotEmpty(getProxyHost())) { + proxy = new HttpHost(getProxyHost(), getProxyPort()); + pcf = new CredentialFactory(getProxyAuthAlias(), getProxyUsername(), getProxyPassword()); + requestConfigBuilder.setProxy(proxy); + httpClientBuilder.setProxy(proxy); + } + + try { + setupAuthentication(pcf, proxy, requestConfigBuilder); + } catch (HttpAuthenticationException e) { + throw new ConfigurationException("exception configuring authentication", e); + } + + httpClientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler(getMaxExecuteRetries())); + + if(areCookiesDisabled()) { + httpClientBuilder.disableCookieManagement(); + } + httpClientBuilder.evictIdleConnections((long) getConnectionIdleTimeout(), TimeUnit.SECONDS); + + sslSocketFactory = getSSLConnectionSocketFactory(); //Configure it here, so we can handle exceptions + + configureRedirectStrategy(); + } + + /** The redirect strategy used to only redirect GET, DELETE and HEAD. */ + private void configureRedirectStrategy() { + if(isFollowRedirects()) { + httpClientBuilder.setRedirectStrategy(new DefaultRedirectStrategy(new String[] { HttpGet.METHOD_NAME, HttpHead.METHOD_NAME, HttpDelete.METHOD_NAME })); + } else { + httpClientBuilder.disableRedirectHandling(); + } + } + + /** + * In order to support multiThreading and connectionPooling. + * The connectionManager has to be initialized with a sslSocketFactory. + * The pool must be re-created once closed. + */ + public void configureConnectionManager() { + int timeToLive = getConnectionTimeToLive(); + if (timeToLive<=0) { + timeToLive = -1; + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS); + log.debug("created PoolingHttpClientConnectionManager with custom SSLConnectionSocketFactory"); + + connectionManager.setMaxTotal(getMaxConnections()); + connectionManager.setDefaultMaxPerRoute(getMaxConnections()); + + if (isStaleChecking()) { + log.info("set up connectionManager, setting stale checking ["+isStaleChecking()+"]"); + connectionManager.setValidateAfterInactivity(getStaleTimeout()); + } + + httpClientBuilder.setConnectionManager(connectionManager); + } + + @Override + public void start() { + buildHttpClient(); + } + + protected void buildHttpClient() { + configureConnectionManager(); + httpClient = httpClientBuilder.build(); + } + + protected void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public boolean isRunning() { + return getHttpClient() != null; + } + + @Override + public void stop() { + //Close the HttpClient and ConnectionManager to release resources and potential open connections + if(httpClient != null) { + try { + httpClient.close(); + } catch (IOException e) { + log.warn("unable to close HttpClient", e); + } + } + } + + private void setupAuthentication(CredentialFactory proxyCredentials, HttpHost proxy, RequestConfig.Builder requestConfigBuilder) throws HttpAuthenticationException { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (StringUtils.isNotEmpty(credentials.getUsername()) || StringUtils.isNotEmpty(getTokenEndpoint())) { + + credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), getCredentials()); + + AuthenticationScheme preferredAuthenticationScheme = getPreferredAuthenticationScheme(); + requestConfigBuilder.setTargetPreferredAuthSchemes(Arrays.asList(preferredAuthenticationScheme.getSchemeName())); + requestConfigBuilder.setAuthenticationEnabled(true); + + if (preferredAuthenticationScheme == AuthenticationScheme.OAUTH) { + OAuthAccessTokenManager accessTokenManager = new OAuthAccessTokenManager(getTokenEndpoint(), getScope(), client_cf, user_cf==null, isAuthenticatedTokenRequest(), this, getTokenExpiry()); + httpClientContext.setAttribute(OAuthAuthenticationScheme.ACCESSTOKEN_MANAGER_KEY, accessTokenManager); + httpClientBuilder.setTargetAuthenticationStrategy(new OAuthPreferringAuthenticationStrategy()); + } + } + if (proxy!=null) { + AuthScope authScope = new AuthScope(proxy, proxyRealm, AuthScope.ANY_SCHEME); + + + if (StringUtils.isNotEmpty(proxyCredentials.getUsername())) { + Credentials httpCredentials = new UsernamePasswordCredentials(proxyCredentials.getUsername(), proxyCredentials.getPassword()); + credentialsProvider.setCredentials(authScope, httpCredentials); + } + log.trace("setting credentialProvider [{}]", credentialsProvider); + + if(isPrefillProxyAuthCache()) { + requestConfigBuilder.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)); + + AuthCache authCache = httpClientContext.getAuthCache(); + if(authCache == null) + authCache = new BasicAuthCache(); + + authCache.put(proxy, new BasicScheme()); + httpClientContext.setAuthCache(authCache); + } + + } + + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + + protected void preAuthenticate() { + if (credentials != null && !StringUtils.isEmpty(credentials.getUsername())) { + AuthState authState = httpClientContext.getTargetAuthState(); + if (authState==null) { + authState = new AuthState(); + httpClientContext.setAttribute(HttpClientContext.TARGET_AUTH_STATE, authState); + } + authState.setState(AuthProtocolState.CHALLENGED); + authState.update(getPreferredAuthenticationScheme().createScheme(), getCredentials()); + } + } + + private Credentials getCredentials() { + String uname; + if (StringUtils.isNotEmpty(getAuthDomain())) { + uname = getAuthDomain() + "\\" + credentials.getUsername(); + } else { + uname = credentials.getUsername(); + } + + return new UsernamePasswordCredentials(uname, credentials.getPassword()); + } + + private AuthenticationScheme getPreferredAuthenticationScheme() { + return StringUtils.isNotEmpty(getTokenEndpoint()) ? AuthenticationScheme.OAUTH : AuthenticationScheme.BASIC; + } + + @Nonnull + protected SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws ConfigurationException { + SSLConnectionSocketFactory sslSocketFactory; + HostnameVerifier hostnameVerifier = verifyHostname ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); + + try { + javax.net.ssl.SSLSocketFactory socketfactory = AuthSSLContextFactory.createSSLSocketFactory(this, this, getProtocol()); + sslSocketFactory = new SSLConnectionSocketFactory(socketfactory, hostnameVerifier); + } catch (Exception e) { + throw new ConfigurationException("cannot create or initialize SocketFactory", e); + } + + // This method will be overwritten by the connectionManager when connectionPooling is enabled! + // Can still be null when no default or an invalid system sslSocketFactory has been defined + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + + return sslSocketFactory; + } + + protected HttpResponse execute(URI targetUri, HttpRequestBase httpRequestBase) throws IOException { + HttpHost targetHost = new HttpHost(targetUri.getHost(), targetUri.getPort(), targetUri.getScheme()); + return getHttpClient().execute(targetHost, httpRequestBase, httpClientContext); + } + + /** + * Timeout in ms of obtaining a connection/result. 0 means no timeout + * @ff.default 10000 + */ + public void setTimeout(int i) { + timeout = i; + } + + /** + * The maximum number of concurrent connections + * @ff.default 10 + */ + public void setMaxConnections(int i) { + maxConnections = i; + } + + /** + * The maximum number of times the execution is retried + * @ff.default 1 (for repeatable messages) else 0 + */ + public void setMaxExecuteRetries(int i) { + maxExecuteRetries = i; + } + + /** Authentication alias used for authentication to the host */ + public void setAuthAlias(String string) { + authAlias = string; + } + + /** Username used for authentication to the host */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Corporate domain name. Should only be used in combination with sAMAccountName, never with an UPN.
+ *
+ * Assuming the following user:
+ * UPN: john.doe@CorpDomain.biz
+ * sAMAccountName: CORPDOMAIN\john.doe
+ *
+ * The username attribute may be set to john.doe
+ * The AuthDomain attribute may be set to CORPDOMAIN
+ */ + @Deprecated + @ConfigurationWarning("Please use the UPN or the full sAM-AccountName instead") + public void setAuthDomain(String string) { + authDomain = string; + } + + /** Password used for authentication to the host */ + public void setPassword(String string) { + password = string; + } + + /** + * Endpoint to obtain OAuth accessToken. If authAlias or username( and password) are specified, + * then a PasswordGrant is used, otherwise a ClientCredentials grant. The obtained accessToken will be added to the regular requests + * in an HTTP Header 'Authorization' with a 'Bearer' prefix. + */ + public void setTokenEndpoint(String string) { + tokenEndpoint = string; + } + /** + * If set to a non-negative value, then determines the time (in seconds) after which the token will be refreshed. Otherwise the token + * will be refreshed when it is half way its lifetime as defined by the expires_in clause of the token response, + * or when the regular server returns a 401 status with a challenge. + * If not specified, and the accessTokens lifetime is not found in the token response, the accessToken will not be refreshed preemptively. + * @ff.default -1 + */ + public void setTokenExpiry(int value) { + tokenExpiry = value; + } + /** Alias used to obtain client_id and client_secret for authentication to tokenEndpoint */ + public void setClientAlias(String clientAuthAlias) { + this.clientAuthAlias = clientAuthAlias; + } + /** Client_id used in authentication to tokenEndpoint */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** Client_secret used in authentication to tokenEndpoint */ + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + /** Space or comma separated list of scope items requested for accessToken, e.g. read write. Only used when tokenEndpoint is specified */ + public void setScope(String string) { + scope = string; + } + /** if set true, clientId and clientSecret will be added as Basic Authentication header to the tokenRequest, instead of as request parameters */ + public void setAuthenticatedTokenRequest(boolean authenticatedTokenRequest) { + this.authenticatedTokenRequest = authenticatedTokenRequest; + } + + + /** Proxy host */ + public void setProxyHost(String string) { + proxyHost = string; + } + + /** + * Proxy port + * @ff.default 80 + */ + public void setProxyPort(int i) { + proxyPort = i; + } + + /** Alias used to obtain credentials for authentication to proxy */ + public void setProxyAuthAlias(String string) { + proxyAuthAlias = string; + } + + /** + * Proxy username + * @ff.default + */ + public void setProxyUsername(String string) { + proxyUsername = string; + } + + /** + * Proxy password + * @ff.default + */ + public void setProxyPassword(String string) { + proxyPassword = string; + } + + /** + * Proxy realm + * @ff.default + */ + public void setProxyRealm(String string) { + proxyRealm = StringUtils.isNotEmpty(string) ? string : null; + } + + /** + * Create a pre-emptive login context for the proxy connection(s). + */ + public void setPrefillProxyAuthCache(boolean b) { + this.prefillProxyAuthCache = b; + } + + /** + * Disables the use of cookies, making the sender completely stateless + * @ff.default false + */ + public void setDisableCookies(boolean disableCookies) { + this.disableCookies = disableCookies; + } + public boolean areCookiesDisabled() { + return disableCookies; + } + + + /** resource URL to keystore or certificate to be used for authentication. If none specified, the JVMs default keystore will be used. */ + @Override + public void setKeystore(String string) { + keystore = string; + } + + @Override + public void setKeystoreType(KeystoreType value) { + keystoreType = value; + } + + @Override + public void setKeystoreAuthAlias(String string) { + keystoreAuthAlias = string; + } + + @Override + public void setKeystorePassword(String string) { + keystorePassword = string; + } + + @Override + public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { + this.keyManagerAlgorithm = keyManagerAlgorithm; + } + + @Override + public void setKeystoreAlias(String string) { + keystoreAlias = string; + } + @Override + public void setKeystoreAliasAuthAlias(String string) { + keystoreAliasAuthAlias = string; + } + @Override + public void setKeystoreAliasPassword(String string) { + keystoreAliasPassword = string; + } + + @Override + /** Resource URL to truststore to be used for authenticating peer. If none specified, the JVMs default truststore will be used. */ + public void setTruststore(String string) { + truststore = string; + } + + @Override + public void setTruststoreAuthAlias(String string) { + truststoreAuthAlias = string; + } + + @Override + public void setTruststorePassword(String string) { + truststorePassword = string; + } + + @Override + public void setTruststoreType(KeystoreType value) { + truststoreType = value; + } + + @Override + public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { + this.trustManagerAlgorithm = trustManagerAlgorithm; + } + + @Override + public void setVerifyHostname(boolean b) { + verifyHostname = b; + } + + @Override + public void setAllowSelfSignedCertificates(boolean allowSelfSignedCertificates) { + this.allowSelfSignedCertificates = allowSelfSignedCertificates; + } + + @Override + public void setIgnoreCertificateExpiredException(boolean b) { + ignoreCertificateExpiredException = b; + } + + /** + * If true, a redirect request will be honoured, e.g. to switch to HTTPS + * @ff.default true + */ + public void setFollowRedirects(boolean b) { + followRedirects = b; + } + + /** + * If true, besides http status code 200 (OK) also the code 301 (MOVED_PERMANENTLY), 302 (MOVED_TEMPORARILY) and 307 (TEMPORARY_REDIRECT) are considered successful + * @ff.default false + */ + public void setIgnoreRedirects(boolean b) { + ignoreRedirects = b; + } + + + /** + * Controls whether connections checked to be stale, i.e. appear open, but are not. + * @ff.default true + */ + public void setStaleChecking(boolean b) { + staleChecking = b; + } + + /** + * Used when StaleChecking=true. Timeout after which an idle connection will be validated before being used. + * @ff.default 5000 ms + */ + public void setStaleTimeout(int timeout) { + staleTimeout = timeout; + } + + /** + * Maximum Time to Live for connections in the pool. No connection will be re-used past its timeToLive value. + * @ff.default 900 s + */ + public void setConnectionTimeToLive(int timeToLive) { + connectionTimeToLive = timeToLive; + } + + /** + * Maximum Time for connection to stay idle in the pool. Connections that are idle longer will periodically be evicted from the pool + * @ff.default 10 s + */ + public void setConnectionIdleTimeout(int idleTimeout) { + connectionIdleTimeout = idleTimeout; + } + + /** + * Secure socket protocol (such as 'SSL' and 'TLS') to use when a SSLContext object is generated. + * @ff.default SSL + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } +} diff --git a/publiccode.yaml b/publiccode.yaml index ddefa3f..1c232ba 100644 --- a/publiccode.yaml +++ b/publiccode.yaml @@ -2,8 +2,9 @@ publiccodeYmlVersion: "0.2" # Instructies: https://github.com/OpenCatalogi/OpenCatalogiBundle/blob/main/docs/Publiccode.md name: WebformulierenVerwerker url: "https://github.com/Sudwest-Fryslan/WebformulierenVerwerker.git" -#softwareVersion: "dev" # Optional -releaseDate: "2023-07-01" +softwareVersion: "6.5.8" # Optional +releaseDate: 2023-07-05 +applicationSuite: het-integratie-platform platforms: - frankframework diff --git a/publiccode_template.yaml b/publiccode_template.yaml new file mode 100644 index 0000000..95033e9 --- /dev/null +++ b/publiccode_template.yaml @@ -0,0 +1,67 @@ +publiccodeYmlVersion: "0.2" +# Instructies: https://github.com/OpenCatalogi/OpenCatalogiBundle/blob/main/docs/Publiccode.md +name: WebformulierenVerwerker +url: "https://github.com/Sudwest-Fryslan/WebformulierenVerwerker.git" +softwareVersion: "${instance_version}" # Optional +releaseDate: ${versionDate_yyyymmdd} +applicationSuite: het-integratie-platform +platforms: + - frankframework + +categories: + - translation + +developmentStatus: stable + +softwareType: "standalone/other" + +description: + en: + shortDescription: > + The CorsaKoppeling component is a SOAP-based web service designed to facilitate interaction between web forms in Kodision and the Corsa system. + Its primary focus is on ensuring statelessness and reliable data saving through the issuance of Corsa registration numbers. + By maintaining loose coupling and providing error tracking capabilities, CorsaKoppeling acts as an intermediary, + offering an esb-like functionality that enhances the robustness and detectability of potential issues. + + longDescription: > + The CorsaKoppeling component is a SOAP-based web service that serves as a crucial intermediary for facilitating seamless interaction between web forms in the Kodision platform and the Corsa system. Its primary objective is to ensure smooth data exchange and reliable data saving processes, while adhering to the principles of statelessness and atomicity. + One of the core features of CorsaKoppeling is its stateless nature. This means that the component does not retain any session-specific information or context between consecutive requests. Each interaction with the web service is treated independently, ensuring that data operations remain independent and self-contained. This statelessness guarantees the integrity and consistency of the data saving process. Upon successful completion of an operation, CorsaKoppeling returns a unique Corsa registration number, serving as a confirmation that the data has been securely saved. + By acting as an intermediary between Kodision and Corsa, CorsaKoppeling facilitates loose coupling between the two systems. Instead of direct communication, Kodision communicates with Corsa via the CorsaKoppeling web service. This decoupling provides several advantages, such as enhanced flexibility, scalability, and maintainability. It allows each system to evolve independently without tightly coupling their functionalities. Furthermore, it simplifies error tracking and debugging processes. When errors occur during data exchange or saving, it is easier to identify and trace the specific component or interaction responsible, enabling efficient issue resolution. + CorsaKoppeling exhibits functionalities similar to an Enterprise Service Bus (ESB). It serves as an integration layer, orchestrating the communication and data flow between Kodision and Corsa. This ESB-like functionality streamlines the overall integration process and ensures reliable and seamless data exchange. It acts as a mediator, abstracting the complexities of direct integration between the two systems and providing a standardized interface that simplifies the development and maintenance efforts. + Overall, the CorsaKoppeling component plays a vital role in enabling effective and secure communication between web forms in Kodision and the Corsa system. Through its stateless and atomic design, it guarantees reliable data saving, while its loose coupling and error tracking capabilities enhance the robustness and detectability of potential issues. By functioning as an intermediary with ESB-like functionalities, CorsaKoppeling streamlines the integration process, promoting flexibility, scalability, and maintainability in the Kodision-Corsa ecosystem. + + features: + - Statelessness + - Atomicity + - Loose Coupling + - Error Tracking + - Standardized Interface + +legal: + license: European Union Public License 1.2 + +maintenance: + type: "community" + + contacts: + - name: Eduard Witteveen + +localisation: + localisationReady: true + availableLanguages: + - en +# De Nederlandse uitbreiding op de Common Ground standaard +nl: + countryExtensionVersion: "1.0" + commonground: + - layerType: "integration" + - installationType: "docker" + - intendedOrganisations: "https://github.com/Sudwest-Fryslan" + gemma: + bedrijfsfuncties: + - "sadsad" + - "sadsad" + bedrijfsservices: + - "sadsad" + - "sadsad" + applicatiefunctie: "referentie component"