diff --git a/.github/workflows/manual-release-test.yml b/.github/workflows/manual-release-test.yml new file mode 100644 index 00000000000..293d39f006b --- /dev/null +++ b/.github/workflows/manual-release-test.yml @@ -0,0 +1,48 @@ +on: workflow_dispatch +name: Test release creation + +jobs: + build: + name: Test uploading release asset + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build project # This would actually build your project, using zip for an example artifact + run: | + RELEASE_DATE=$( date +%Y%m%d%H%M ) + echo "RELEASE_DATE=${RELEASE_DATE}" >>${GITHUB_ENV} + mkdir -p dist/build-${RELEASE_DATE} + echo "humba" >dist/build-${RELEASE_DATE}/build-${RELEASE_DATE}.tar.gz + echo "trala" >dist/build-${RELEASE_DATE}/release-notes.txt + echo "SIMPLE_VERSION_INFO=$( basename dist/* )" >>${GITHUB_ENV} + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.SIMPLE_VERSION_INFO }} + release_name: ${{ env.SIMPLE_VERSION_INFO }} + draft: false + prerelease: false + - name: Upload Release Asset tar.gz + id: upload-release-asset-tar-gz + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/${{ env.SIMPLE_VERSION_INFO }}.tar.gz + asset_name: ${{ env.SIMPLE_VERSION_INFO }}.tar.gz + asset_content_type: application/x-tar + - name: Upload Release Asset release_notes.txt + id: upload-release-asset-release-notes-txt + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/release-notes.txt + asset_name: release-notes.txt + asset_content_type: text/plain diff --git a/.github/workflows/play-with-docker.yml b/.github/workflows/play-with-docker.yml new file mode 100644 index 00000000000..cb37118a78d --- /dev/null +++ b/.github/workflows/play-with-docker.yml @@ -0,0 +1,47 @@ +name: play-with-docker +on: + workflow_dispatch: + inputs: + release: + description: Release + default: '' +jobs: + play-with-docker-job: + #runs-on: ubuntu-latest-64cpu + #runs-on: ubuntu-latest-4cpu + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Login to docker.sapsailing.com + uses: docker/login-action@v3 + with: + registry: docker.sapsailing.com + username: ${{ secrets.DOCKER_SAPSAILING_COM_USERNAME }} + password: ${{ secrets.DOCKER_SAPSAILING_COM_PASSWORD }} + - name: Copy a few files + shell: bash + run: | + cp "java/target/env.sh" docker/ + cp "java/target/start" docker/ + cp "java/target/configuration/JavaSE-11.profile" docker/ + - name: Determine release + shell: bash + run: | + echo "RELEASE=$( if [ "${{ github.event.inputs.release }}" = "" ]; then + wget -O - https://releases.sapsailing.com/ 2>/dev/null | grep 'build-[0-9]\{10\}' | tail -1 | sed -e 's/^.*\(build-[0-9]*\).*$/\1/' + else + echo "${{ github.event.inputs.release }}" + fi )" >>${GITHUB_ENV} + - name: Build & push Docker image + uses: docker/build-push-action@v5 + with: + build-args: RELEASE=${{ env.RELEASE }} + tags: docker.sapsailing.com/test:${{ env.RELEASE }} + file: docker/Dockerfile + context: docker/ + push: true + - name: Clean up copied files + shell: bash + run: rm docker/start docker/env.sh docker/JavaSE-11.profile + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..c60a8d8f801 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,105 @@ +name: release +on: + push: + paths-ignore: + - 'wiki/**' + - '.github/workflows/*' + - '**/*.md' + workflow_dispatch: {} +jobs: + build: + runs-on: ubuntu-latest-64cpu + #runs-on: ubuntu-latest-4cpu + steps: + - name: Check out the repository to the runner + uses: actions/checkout@v4 + with: + fetch-depth: '500' + - name: Install JDK 8 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '8' + mvn-toolchain-id: 'JavaSE-1.8' + - name: Install JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + mvn-toolchain-id: 'JavaSE-17' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + log-accepted-android-sdk-licenses: 'false' + cmdline-tools-version: 9123335 # This corresponds to human-friendly version number 8.0 + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.10.0 + with: + mongodb-version: 4.4 + - name: Start RabbitMQ + uses: namoshek/rabbitmq-github-action@v1 + with: + version: '3.8.9' + ports: '5672:5672' + - shell: bash + env: # Or as an environment variable + APP_PARAMETERS: "-Daws.region=eu-west-1 -Dgoogle.maps.authenticationparams=${{ secrets.GOOGLE_MAPS_AUTHENTICATIONPARAMS }} -Daws.s3.test.s3AccessId=${{ secrets.AWS_S3_TEST_S3ACCESSID }} -Daws.s3.test.s3AccessKey=${{ secrets.AWS_S3_TEST_S3ACCESSKEY }}" + JAVA8_HOME: ${{env.JAVA_HOME_8_X64}} + run: | + ./configuration/buildAndUpdateProduct.sh -x 60 build 2>&1 | tee build.log + - name: Collect Test Reports + uses: dorny/test-reporter@v1 + if: always() + with: + name: Maven Tests + path: '**/TEST-*.xml' + max-annotations: '1' + list-suites: failed + list-tests: failed + reporter: java-junit + fail-on-error: true + - shell: bash + run: | + ./configuration/buildAndUpdateProduct.sh -u -L release + - name: Determine release name + run: | + echo "SIMPLE_VERSION_INFO=$( basename dist/* )" >>${GITHUB_ENV} + - name: Upload distribution artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.SIMPLE_VERSION_INFO }}.zip + path: dist/**/* + retention-days: 90 + - name: Create Release + id: create_release + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-17' || startsWith(github.ref, 'refs/heads/releases/') }} + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.SIMPLE_VERSION_INFO }} + release_name: ${{ env.SIMPLE_VERSION_INFO }} + draft: false + prerelease: false + - name: Upload Release Asset tar.gz + id: upload-release-asset-tar-gz + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-17' || startsWith(github.ref, 'refs/heads/releases/') }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/${{ env.SIMPLE_VERSION_INFO }}.tar.gz + asset_name: ${{ env.SIMPLE_VERSION_INFO }}.tar.gz + asset_content_type: application/x-tar + - name: Upload Release Asset release_notes.txt + id: upload-release-asset-release-notes-txt + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker-17' || startsWith(github.ref, 'refs/heads/releases/') }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./dist/${{ env.SIMPLE_VERSION_INFO }}/release-notes.txt + asset_name: release-notes.txt + asset_content_type: text/plain diff --git a/.github/workflows/reuse-tool-license-updates.yml b/.github/workflows/reuse-tool-license-updates.yml new file mode 100644 index 00000000000..e7b66618483 --- /dev/null +++ b/.github/workflows/reuse-tool-license-updates.yml @@ -0,0 +1,15 @@ +name: "Launch Metadata Creation Tool for REUSE" +on: + workflow_dispatch: ~ + +jobs: + create_metadata_proposal: + runs-on: ubuntu-latest + name: "Metadata Creation Tool" + steps: + - uses: SAP/metadata-creation-tool-for-reuse@main + with: + repository_url: "${{ github.server_url }}/${{ github.repository }}" + access_token: "${{ secrets.REUSE_ACCESS_TOKEN }}" + copyright_owner: "SAP SE or an SAP affiliate company" + upstream_contact: "Axel Uhl " diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 00000000000..2f2b5f1da52 --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,60 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: sailing-analytics +Upstream-Contact: SAP Open Source Program Office ospo@sap.com +Source: https://github.com/SAP/sailing-analytics + +Files: java/com.google.gwt.servlet/* +Copyright: 2024 com.google.gwt.servlet contributors. +License: Apache-2.0 + +Files: java/net.sf.marineapi/doc/* +Copyright: 2024 doc contributors. +License: GPL-3.0-only + +Files: java/com.google.gwt.user/* +Copyright: 2024 com.google.gwt.user contributors. +License: Apache-2.0 + +Files: java/com.googlecode.java-diff-utils.test/* +Copyright: 2024 com.googlecode.java-diff-utils.test contributors. +License: Apache-2.0 + +Files: * +Copyright: 2024 SAP SE or an SAP affiliate company and sailing-analytics contributors. +License: Apache-2.0 + +Files: java/com.google.gwt.dev/* +Copyright: 2024 com.google.gwt.dev contributors. +License: Apache-2.0 + +Files: java/org.mp4parser.isoparser/* +Copyright: 2024 org.mp4parser.isoparser contributors. +License: Apache-2.0 + +Files: java/elemental2/* +Copyright: 2024 elemental2 contributors. +License: Apache-2.0 + +Files: java/org.json.simple/* +Copyright: 2024 org.json.simple contributors. +License: MIT + +Files: java/org.openqa.selenium.osgi/* +Copyright: 2024 org.openqa.selenium.osgi contributors. +License: Apache-2.0 + +Files: java/com.google.gwt.ajaxloader/* +Copyright: 2024 com.google.gwt.ajaxloader contributors. +License: Apache-2.0 + +Files: java/com.googlecode.java-diff-utils/* +Copyright: 2024 com.googlecode.java-diff-utils contributors. +License: Apache-2.0 + +Files: java/com.sap.sailing.gwt.ui/qrcode/* +Copyright: 2024 qrcode contributors. +License: MIT + +Files: ios/SAPTracker/src/xcode/SAPTracker/flags/* +Copyright: 2024 flags contributors. +License: MIT diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..bb9b3d7d89c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,171 @@ +# Contributing to the Sailing Analytics + +Thank you for your interest in the Sailing Analytics! We welcome contributions in all forms. + +Here are some of the ways you can contribute to Sailing Analytics + +Note: This is a living document. Like the Sailing Analytics, this document will be improved over time with the help of our community. Feedback welcome. + +---- + +## Reporting Issues + +Bug reports welcome! We are currently still using [Bugzilla](https://bugzilla.sapsailing.com) for tracking user stories and bugs. + +### Determining if an Issue Should be Created + +Analyzing reports is a bit of work, so please ask yourself these questions before creating an issue: + +* Does the bug really apply to Sailing Analytics + + There may be reasons the problem is occurring that have nothing to do with the Sailing Anlaytics code. Consider the following: + + * The problem may be caused by code that is not part of Sailing Analytics. + * The problem may be caused by data being sent to Sailing Analytics through REST API calls. + * The behavior may be different from what you expect, but may be working as designed. Or the feature may need improvement. In this case, it would be an enhancement request. Feature requests can also be submitted to [Bugzilla](https://bugzilla.sapsailing.com). + +* Is the problem reproducible in the latest release? + + * If you are not using the latest libraries or source code, please try to reproduce with the latest code first. + * Make sure the problem is consistently reproducible using repeatable steps. +* Has this bug already been reported? + * Please search the issue tracker for a similar bug before reporting the issue as a new bug. + +If the bug does indeed apply to Sailing Analytics code, please create an issue. + +### Providing the Right Information for the Issue + +* Summarize the issue well. The following are some guidelines: + + * Precisely state what you expected as compared to the actual behavior you're seeing. + * Include only those details that apply to the issue. + * Be concise, but include details important for helping us in understanding the problem and finding the root cause. + * State the version you are testing, which browser you are using, and on which device. + + If it is possible to indicate what you've seen in other browser and device combinations, that is even better! For example, does the issue occur in all browsers, or just one browser on one particular operating system? (For example, "only in Firefox on Windows 10.) + * If possible, include the last version where the bug was not present. + * If the bug is more visual, please attach a screenshot and mark up the problem. + * Generally, provide as much detail as necessary, but balance our need for information with how obvious the problem is. + +* Provide detailed, step-by-step instructions for reproducing the issue with an example, including, if possible: + + * A URL to your example + * Screenshots if it helps us understand better. + * Do not include more than one bug per issue created. This helps us to analyze bugs more easily. + +Please report issues in English. + +### About Reporting Security Issues + +If you find a security issue, we'd like to encourage you to tell us directly instead of creating a public issue. This way we can fix it before it can be exploited. + +* Researchers/non-Customers: please send the relevant information to support@sapsailing.com. + +### How We Process Issues + +New issues are reviewed regularly for validity and prioritization. Confirmed issues are given the "Approved" label, and the rest are either sent back to the reporter with a request for more details, or closed with an explanation. + +Validated issues are then moved into one of these buckets: + +* Priority issues will be assigned to one of our developers +* Issues that are less urgent will be left open as "contribution welcome" +* Certain issues may be moved to our internal issue tracking system if we don't think they belong to the Sailing Analytics. + +Issues are closed when the fix is merged to develop. The release that contains the fix will be noted in the comments and the release changelog. + +### How We Use Bugzilla Priorities + +Our Bugzilla set-up supports five priorities (1-5): + +* "1" is meant for show stoppers where, e.g., the website locks up, wrong data is being reported or the application crashes. Issues with thie priority need immediate action to unblock a live event using the solution. +* "2" is used for things that definitely need fixing but that are not necessarily holding up general operations at a live event. +* "3" identifies nice to have things we would like to see fixed at some point, but there is no specific urgency. Topics with this priority may be good candidates to start working on the project. +* "4" and "5" are what you use to make sure an idea is recorded and not forgotten, but other than when searching for a neat onboarding task for someone who would like to join the project, tasks with these priorities are hardly ever looked at. + +### Issue Reporting Disclaimer + +Feedback, especially bug reports, are always welcome. However, our capacity as a team is limited -- we cannot answer specific project or consultation requests, nor can we invest time in fleshing out what might be a bug. Because of this, we reserve the right to close or not process issue reports that do not contain enough information. We also do not guarantee that every well-documented issue will be fixed. + +That being said, we will try our very best to ensure the Sailing Analytics codebase is of high quality. + +---- + +## Contributing Code + +We welcome contributions to the Sailing Analytics codebase. Before you start your first contribution, here are some things you should know: + +1. You must be aware of the Apache License (which describes contributions), and you must agree to the [Contributors License Agreement](LICENSE.md). This is common practice for most open source projects. + + * For company contributors, special rules apply. See the respective section below for details. + +1. Contributions must be compliant with the project code style, quality, and standards. We also follow them :-) + + The `Contribution Content Guidelines` section below gives more details on the coding guidelines. + +1. Not all contributions will be accepted. + * The code you are submitting must fit the overall vision and direction of the Sailing Analytics and really improve it. Bug fixes are simple cases, for example, but new features may work better as third-party extensions. + * Major feature implementations should be discussed on the issue tracker. + +### Contributor License Agreement + +When you contribute anything to the Sailing Analytics (code, documentation, analysis, anything), be aware that your contribution is covered by the same [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) that is applied to the Sailing Analytics itself. + +This applies to all contributors, including those contributing on behalf of a company. + +### Developer Certificate of Origin (DCO) + +Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). + +#### Company Contributors + +If employees of a company contribute code, in **addition** to the individual agreement above, there needs to be one company agreement submitted. This is mainly for the protection of the contributing employees. + +A company representative authorized to do so must fill out the following [Corporate Contributor License Agreement](/docs/SAP%20Corporate%20Contributor%20License%20Agreement.pdf) form. The form contains a list of employees who are authorized to contribute on behalf of your company. When this list changes, please let us know. + +Submit the form to us through one of the following methods: + +* Email to [opensource@sap.com](mailto:opensource@sap.com) and [support@sapsailing.com](mailto:support@sapsailing.com) +* Fax to +49 6227 78-45813 +* Mail to: + + Industry Standards & Open Source Team + Dietmar-Hopp-Allee 16 + 69190 Walldorf, Germany + +### Contribution Content Guidelines + + A contribution will be considered for inclusion in the Sailing Analytics if it meets the following criteria: + +* The contribution fits the overall vision and direction of the Sailing Analytics +* The contribution truly improves the solution +* The contribution follows the applicable guidelines and standards. + + The "guidelines and standards" requirement could fill entire books and still lack a 100% clear definition, but rest assured that you will receive feedback if something is not right. That being said, please consult the [Onboarding Document](https://wiki.sapsailing.com/wiki/howto/onboarding). + +### Contribution Process + + 1. Make sure the change would be welcome, as described above. + + 1. Create a fork of the Sailing Analytics sources. + + 1. Work on the change in your fork (either on the `master` branch or on a feature branch, typically named after the Bugzilla issue, such as ``bug1234``). + + 1. Commit and push your changes. + + 1. Give reasonable commit messages; a short first line, and if you'd like to write additional, more verbose comments, finish the first line with a semicolon, add an empty line, then add more comments. + + 1. If your change fixes an issue reported in Bugzilla, add the following line to the commit message: + + ```bug{issueNumber}: ...``` + + 1. Create a pull request so that we can review your change. + 1. Wait for our code review and approval, possibly enhancing your change on request. + + Note: This may take time, depending on the required effort for reviewing, testing, and clarification. Sailing Analytics developers are also working their regular duties. + +1. After the change has been approved, we will inform you in a comment. + +1. Due to internal SAP processes, your pull request cannot be merged directly into the branch. It will be merged internally, and will also immediately appear in the public repository. +1. We will close the pull request. At that point, you can delete your branch. + +We look forward to hearing from you! diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..ef37159e3b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] [SAP SE or an SAP affiliate company and sailing-analytics contributors] + + 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. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000000..137069b8238 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000000..2071b23b0e0 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index c4680e2dd9d..6ca98e0c1e9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,51 @@ # SAP Sailing Analytics -The SAP Sailing Analytics platform as seen on [sapsailing.com](https://sapsailing.com) +## About this Project + +The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. + +## Description + +This is the software running the SAP Sailing Analytics platform as seen on [sapsailing.com](https://sapsailing.com) Sailing provides the perfect platform for SAP to showcase solutions and help the sport run like never before. SAP’s involvement in the sport has transformed the sailing experience by providing tools, which: -Help sailors analyze performance and optimize strategy -Bring fans closer to the action -Provide the media with information and insights to deliver a greater informed commentary +- Help sailors analyze performance and optimize strategy +- Bring fans closer to the action +- Provide the media with information and insights to deliver a greater informed commentary + SAP has a longstanding involvement with sailing and has established a portfolio spanning across teams and regattas. -Start contributing: [https://wiki.sapsailing.com/wiki/howto/onboarding](https://wiki.sapsailing.com/wiki/howto/onboarding) +## Requirements + +The software can be run on any Linux or Windows machine with ``bash`` installed; it has also been compiled successfully for the ARM platform and was deployed to a Raspberry Pi computer. As a database, MongoDB is required, tested with releases 4.4, 5.0, and 6.0. For use in a replicated scenario (scale-out, high availability), RabbitMQ is required. Docker Compose can be used to tie these three components together, as Docker images are produced on a regular basis. + +Compute node and database sizing depends on several aspects of your workloads, such as whether live or replay data is to be served, how many different classes with separate leaderboard are racing concurrently, how many competitors are racing in each class, or how many concurrent viewers produce how many requests and which type (e.g., analytical, data mining, or watching a live race). + +To quantify this at least approximately, here are a few examples for typical node sizes that can handle such types of events reasonably well: + +- National sailing league event; six boats, 18 competitors, a single live leaderboard: 8GB of RAM and 4-8 CPUs +- Large multi-class event with 15 classes with their separate leaderboards, concurrently racing on six course areas: 16GB of RAM, 16 CPUs +- Archive of 30,000 races with a few thousand visitors per day with varying analytical and replay workloads: 64GB RAM, 2TB NVMe swap, 8 CPUs + +A single node typically handles up to 500 concurrent viewers for live events. You will want to scale out accordingly, using the replication pattern offered by the solution which uses RabbitMQ for transaction log shipping. + +## Contributing + +To start contributing, read the onboarding document at the following URL: [https://wiki.sapsailing.com/wiki/howto/onboarding](https://wiki.sapsailing.com/wiki/howto/onboarding). The project welcomes contributions in the form of pull requests, for example, enhancements of the Data Mining functionality, including any sailing-specific metric or dimension you may think of and that you find is still missing so far; or additional features for the race viewer; or a map visualization that does not require a Google Map but uses Open Street Map / Open Layers; landscape automation; improved start sequence analytics; major UI improvements for the administrative layer ("AdminConsole"), etc. + +The issue tracker at [https://bugzilla.sapsailing.com](https://bugzilla.sapsailing.com) is currently used for any sort of issue and enhancement request tracking. Help to migrate this smoothly to Github Issues would be much appreciated, ideally keeping issue numbers stable due to many references to those Bugzilla bug numbers, be it in the source code, the Wiki, or the build infrastructure. + + +## Code of Conduct + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](https://github.com/SAP/.github/blob/main/CODE_OF_CONDUCT.md) at all times. + +## Licensing + +Copyright 2024 SAP SE or an SAP affiliate company and sailing-analytics contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/sailing-analytics). + +See [here](https://www.sapsailing.com/gwt/Home.html#/imprint/:) for a list of components used by the project, as well as their licenses, also to be found in the file ``java/com.sap.sailing.gwt.ui/imprint.json``. ## Building and Running @@ -33,8 +69,17 @@ At [https://releases.sapsailing.com](https://releases.sapsailing.com) you find o cd sailinganalytics echo "MONGODB_URI=mongodb://localhost/winddb" | ${GIT_ROOT}/java/target/refreshInstance.sh auto-install-from-stdin ``` + This will download and install the latest release and configure it such that it will connect to a MongoDB server running locally (``localhost``) and listening on the default port ``27017``, using the database called ``winddb``. +In addition to the necessary ``MONGODB_URI`` variable you may need to inject a few secrets into your runtime environment: + +- ``MANAGE2SAIL_ACCESS_TOKEN`` access token for result and regatta structure import from the Manage2Sail regatta management system +- ``IGTIMI_CLIENT_ID`` / ``IGTIMI_CLIENT_SECRET`` credentials for ``igtimi.com`` in case you have one or more WindBot devices that you would like to integrate with +- ``GOOGLE_MAPS_AUTHENTICATION_PARAMS`` as in ``"key=..."`` or ``"client=..."``, required to display the Google Map in the race viewer. Obtain a Google Maps key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard). + +In the ``configuration/mail.properties`` file make the necessary adjustments in case you would like to enable the application to send out e-mails, e.g., for user notifications or invitations. + Launch the server from the directory to which you just installed it: ``` ./start @@ -51,6 +96,9 @@ To build a docker image, try ``docker/makeImageForLatestRelease``. The upload to ``` docker run -d -e "MEMORY=4g" -e "MONGODB_URI=mongodb://my.mongohost.org?replicaSet=rs0&retryWrites=true" -P ``` + +As explained above for the non-Docker scenario, add any variable assignments for secrets you need to pass to the runtime by adding more ``-e`` arguments. + Do a "docker ps" to figure out the port exposing the web application: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 79f6faf19b6a docker.sapsailing.com/sapsailing:latest "/home/sailing/serve…" 33 seconds ago Up 32 seconds 0.0.0.0:32782->6666/tcp, 0.0.0.0:32781->7091/tcp, 0.0.0.0:32780->8000/tcp, 0.0.0.0:32779->8888/tcp, 0.0.0.0:32778->14888/tcp modest_dhawan diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index a17b2236f06..b2d9df28ae4 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -127,6 +127,7 @@ if [ $# -eq 0 ]; then echo "buildAndUpdateProduct [-b -u -g -t -a -r -o -c -p -v -m -n -l -x -j ] [build|install|all|hot-deploy|remote-deploy|local-deploy|release]" echo "" echo "-g Disable GWT compile, no gwt files will be generated, old ones will be preserved." + echo "-G Build forked GWT and gwt-maven-plugin locally instead of downloading Github release." echo "-b Build GWT permutation only for one browser and English language." echo "-t Disable tests" echo "-a Disable mobile projects (RaceCommittee App, e.g., in case no AndroidSDK is installed)" @@ -140,6 +141,7 @@ if [ $# -eq 0 ]; then echo " com.sap.sailing.monitoring. Only works if there is a fully built server available." echo " This parameter can also hold the name of the release if you are using the release command." echo "-l Telnet port the OSGi server is running. Optional but enables fully automatic hot-deploy." + echo "-L in conjunction with the release sub-command, build the release only locally to dist/ and do not upload" echo "-s Name of server you want to use as target for install, hot-deploy or remote-reploy. This overrides default behaviour." echo "-w Target for remote-deploy and release. Must comply with the following format: user@server." echo "-u Run without confirmation messages. Use with extreme care." @@ -182,11 +184,12 @@ echo SERVERS_HOME is $SERVERS_HOME echo BRANCH is $active_branch echo VERSION is $VERSION_INFO -options=':bgtocpaArvm:n:l:s:w:x:j:u' +options=':bgtocpaArvmLG:n:l:s:w:x:j:u' while getopts $options option do case $option in g) gwtcompile=0;; + G) BUILD_GWT_FORK=1;; t) testing=0;; b) onegwtpermutationonly=1;; o) offline=1;; @@ -199,6 +202,7 @@ do m) MAVEN_SETTINGS=$OPTARG;; n) OSGI_BUNDLE_NAME=$OPTARG;; l) OSGI_TELNET_PORT=$OPTARG;; + L) LOCAL_RELEASE_ONLY=1;; s) TARGET_SERVER_NAME=$OPTARG HAS_OVERWRITTEN_TARGET=1;; w) REMOTE_SERVER_LOGIN=$OPTARG;; @@ -358,12 +362,16 @@ INSTALL_FROM_RELEASE=$SIMPLE_VERSION_INFO echo "Packaged release $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO.tar.gz! I've put an env.sh that matches the current branch to $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO/env.sh!" - echo "Checking the remote connection..." - REMOTE_HOME=`ssh $REMOTE_SERVER_LOGIN 'echo $HOME/releases'` - echo "Now uploading release to $REMOTE_SERVER_LOGIN:$REMOTE_HOME. Can take quite a while!" + if [ "${LOCAL_RELEASE_ONLY}" != "1" ]; then + echo "Checking the remote connection..." + REMOTE_HOME=`ssh $REMOTE_SERVER_LOGIN 'echo $HOME/releases'` + echo "Now uploading release to $REMOTE_SERVER_LOGIN:$REMOTE_HOME. Can take quite a while!" - `which scp` -r $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO $REMOTE_SERVER_LOGIN:$REMOTE_HOME/ - echo "Uploaded release to $REMOTE_HOME! Make sure to also put an updated env.sh if needed to the right place ($REMOTE_HOME/environment in most cases)" + `which scp` -r $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO $REMOTE_SERVER_LOGIN:$REMOTE_HOME/ + echo "Uploaded release to $REMOTE_HOME! Make sure to also put an updated env.sh if needed to the right place ($REMOTE_HOME/environment in most cases)" + else + echo "Release available at $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO/, tarball at $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO/$SIMPLE_VERSION_INFO.tar.gz" + fi fi if [[ "$@" == "local-deploy" ]]; then @@ -748,8 +756,13 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then extra="$extra -P with-not-android-relevant,!with-mobile" if [ $gwtcompile -eq 1 ]; then - echo "Building and installing forked GWT version..." - JAVA_HOME="${JAVA8_HOME}" `dirname $0`/install-gwt "${PROJECT_HOME}" + if [ "${BUILD_GWT_FORK}" = "1" ]; then + echo "Building and installing forked GWT version..." + JAVA_HOME="${JAVA8_HOME}" `dirname $0`/install-gwt "${PROJECT_HOME}" + else + echo "Downloading and installing forked GWT version..." + `dirname $0`/install-gwt-from-fork-releases https://github.com/SAP/gwt-forward-serialization-rpc https://github.com/SAP/gwt-maven-plugin-forward-serialization-rpc 2.11.0 . + fi fi echo "Using following command: mvn $extra -DargLine=\"$APP_PARAMETERS\" -fae -s $MAVEN_SETTINGS $clean install" echo "Maven version used: `mvn --version`" diff --git a/configuration/install-gwt-from-fork-releases b/configuration/install-gwt-from-fork-releases new file mode 100755 index 00000000000..fd524fbd3dd --- /dev/null +++ b/configuration/install-gwt-from-fork-releases @@ -0,0 +1,30 @@ +#!/bin/bash +# Downloads releases of GWT and the gwt-maven-plugin and installs them to the local Maven repo at ~/.m2 +# as well as copies the GWT JARs to the sailing Git workspace +# Usage: install-gwt-from-fork-releases {gwt-repo-URL} {gwt-maven-plugin-repo-URL} {gwt-version} {sailing-git-root} +# Example: install-gwt-from-fork-releases https://github.com/axeluhl/gwt https://github.com/axeluhl/gwt-maven-plugin 2.11.0 . +GWT_REPO_URL="${1}" +GWT_MAVEN_PLUG_REPO_URL="${2}" +GWT_VERSION="${3}" +SAILING_GIT_ROOT="$( cd ${4}; pwd )" +GWT="`mktemp -t -d gwt.XXXXX`" +cd "${GWT}" +echo "GWT directory: ${GWT}" +curl -L "${GWT_REPO_URL}/releases/download/gwt-${GWT_VERSION}/gwt-${GWT_VERSION}.zip" -o "gwt-${GWT_VERSION}.zip" +curl -L "${GWT_MAVEN_PLUG_REPO_URL=}/releases/download/gwt-${GWT_VERSION}/gwt-maven-plugin-${GWT_VERSION}.jar" -o "gwt-maven-plugin-${GWT_VERSION}.jar" +curl -L "${GWT_MAVEN_PLUG_REPO_URL=}/releases/download/gwt-${GWT_VERSION}/gwt-maven-plugin-${GWT_VERSION}.pom" -o "gwt-maven-plugin-${GWT_VERSION}.pom" +unzip gwt-${GWT_VERSION}.zip +cd gwt-${GWT_VERSION} +cp gwt-dev.jar ${SAILING_GIT_ROOT}/java/com.google.gwt.dev/lib/gwt-dev.jar +cp gwt-user.jar ${SAILING_GIT_ROOT}/java/com.google.gwt.user/lib/gwt-user.jar +cp gwt-servlet.jar ${SAILING_GIT_ROOT}/java/com.google.gwt.servlet/lib/gwt-servlet.jar +cp gwt-servlet-deps.jar ${SAILING_GIT_ROOT}/java/com.google.gwt.servlet/lib/gwt-servlet-deps.jar +for i in dev user servlet; do + mvn --batch-mode install:install-file -Dfile=gwt-${i}.jar -DpomFile="${SAILING_GIT_ROOT}/configuration/gwt-poms/gwt-$i-${GWT_VERSION}.pom" +done +mvn --batch-mode install:install-file -Dpackaging=pom -Dfile="${SAILING_GIT_ROOT}/configuration/gwt-poms/gwt-${GWT_VERSION}.pom" -DpomFile="${SAILING_GIT_ROOT}/configuration/gwt-poms/gwt-${GWT_VERSION}.pom" +mvn --batch-mode install:install-file -Dpackaging=pom -Dfile="${SAILING_GIT_ROOT}/configuration/gwt-poms/requestfactory-${GWT_VERSION}.pom" -DpomFile="${SAILING_GIT_ROOT}/configuration/gwt-poms/requestfactory-${GWT_VERSION}.pom" +# gwt-maven-plugin: +cd .. +mvn --batch-mode install:install-file -Dfile="gwt-maven-plugin-${GWT_VERSION}.jar" -DpomFile="gwt-maven-plugin-${GWT_VERSION}.pom" +rm -rf "${GWT}" diff --git a/configuration/tractrac-json-urls b/configuration/tractrac-json-urls index 8bf203f023b..d42283414c9 100644 --- a/configuration/tractrac-json-urls +++ b/configuration/tractrac-json-urls @@ -513,6 +513,7 @@ https://event.tractrac.com/events/event_20230531_ERERFXNACR/jsonservice.php https://event.tractrac.com/events/event_20230611_KielerWoch/jsonservice.php https://event.tractrac.com/events/event_20230622_KielerWoch/jsonservice.php https://event.tractrac.com/events/event_20240312_TrofeoPrin/jsonservice.php +https://event.tractrac.com/events/event_20240411_SemaineOly/jsonservice.php http://skitrac.traclive.dk/events/event_20140821_ESSCardiff/jsonservice.php http://skitrac.traclive.dk/events/event_20140909_ESSIstanbu/jsonservice.php http://skitrac.traclive.dk/events/event_20141001_ESSNice/jsonservice.php @@ -533,6 +534,7 @@ http://skitrac.traclive.dk/events/event_20160727_ESSHamburg/jsonservice.php http://skitrac.traclive.dk/events/event_20160921_ESSInstanb/jsonservice.php http://skitrac.traclive.dk/events/event_20161005_ESSLisbon/jsonservice.php https://paris1.tractrac.com/events/event_20240312_TrofeoPrin/jsonservice.php +https://paris1.tractrac.com/events/event_20240411_SemaineOly/jsonservice.php http://traclive.dk/events/event_20120905_erEuropean/jsonservice.php http://traclive.dk/events/event_20130302_ESSMuscat/jsonservice.php http://traclive.dk/events/event_20130401_STGTrainin/jsonservice.php diff --git a/docker/Dockerfile b/docker/Dockerfile index 796f0b19a12..7e51e9e16ed 100755 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.sapsailing.com/sapjvm8:8.1.097 +FROM docker.sapsailing.com/sapjvm8:8.1.098 ARG RELEASE LABEL maintainer=axel.uhl@sap.com # Download and extract the release @@ -9,7 +9,7 @@ COPY vimrc /root/.vimrc RUN wget -O /tmp/rds.pem https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem \ && /opt/sapjvm_8/bin/keytool -importcert -alias AWSRDS -file /tmp/rds.pem -keystore /opt/sapjvm_8/jre/lib/security/cacerts -noprompt -storepass changeit \ && rm /tmp/rds.pem -RUN wget -O /tmp/${RELEASE}.tar.gz http://releases.sapsailing.com/${RELEASE}/${RELEASE}.tar.gz \ +RUN wget -O /tmp/${RELEASE}.tar.gz https://releases.sapsailing.com/${RELEASE}/${RELEASE}.tar.gz \ && tar xzvpf /tmp/${RELEASE}.tar.gz \ && rm /tmp/${RELEASE}.tar.gz COPY env.sh . diff --git a/docker/Dockerfile_sapjvm b/docker/Dockerfile_sapjvm index ebdcddbd098..431ac6bef81 100644 --- a/docker/Dockerfile_sapjvm +++ b/docker/Dockerfile_sapjvm @@ -1,5 +1,5 @@ FROM buildpack-deps:bullseye -ARG SAPJVM_VERSION=8.1.097 +ARG SAPJVM_VERSION=8.1.098 LABEL maintainer=axel.uhl@sap.com # Download and extract the SAP JVM 8 ENV PATH=${PATH}:/opt/sapjvm_8/bin @@ -8,7 +8,7 @@ WORKDIR /opt RUN apt-get update \ && apt-get install -y wget apt-utils unzip \ && apt-get -y upgrade \ - && curl --cookie eula_3_1_agreed=tools.hana.ondemand.com/developer-license-3_1.txt "https://tools.hana.ondemand.com/additional/sapjvm-${SAPJVM_VERSION}-linux-x64.zip" --output sapjvm8-linux-x64.zip \ + && curl --cookie eula_3_2_agreed=tools.hana.ondemand.com/developer-license-3_2.txt "https://tools.hana.ondemand.com/additional/sapjvm-${SAPJVM_VERSION}-linux-x64.zip" --output sapjvm8-linux-x64.zip \ && unzip sapjvm8-linux-x64.zip \ && rm sapjvm8-linux-x64.zip \ && echo "export JAVA_HOME=/opt/sapjvm_8; export PATH=\${PATH}:/opt/sapjvm_8/bin" > /etc/profile.d/javahome.sh diff --git a/docker/Dockerfile_windestimation b/docker/Dockerfile_windestimation index fd1d577d4dc..3fd945bde5c 100755 --- a/docker/Dockerfile_windestimation +++ b/docker/Dockerfile_windestimation @@ -1,4 +1,4 @@ -ARG SAPJVM_VERSION=8.1.097 +ARG SAPJVM_VERSION=8.1.098 FROM docker.sapsailing.com/sapjvm8:${SAPJVM_VERSION} LABEL maintainer=axel.uhl@sap.com # Download and extract the release diff --git a/docker/makeImageForLatestRelease b/docker/makeImageForLatestRelease index 381858cacc3..3b72d4568c8 100755 --- a/docker/makeImageForLatestRelease +++ b/docker/makeImageForLatestRelease @@ -2,7 +2,7 @@ release=$1 if [ "${release}" = "" ]; then SET_LATEST=1 - release=$(wget -O - http://releases.sapsailing.com/ 2>/dev/null | grep 'build-[0-9]\{10\}' | tail -1 | sed -e 's/^.*\(build-[0-9]*\).*$/\1/') + release=$(wget -O - https://releases.sapsailing.com/ 2>/dev/null | grep 'build-[0-9]\{10\}' | tail -1 | sed -e 's/^.*\(build-[0-9]*\).*$/\1/') echo Latest release is ${release} fi GITROOT="`dirname $0`/.." @@ -15,7 +15,7 @@ cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR" cd "$DOCKERDIR" docker build --build-arg RELEASE=${release} -t sapsailing:${release} . echo "Cleaning up..." -rm start env.sh JavaSE-11.profile Dockerfile +rm start env.sh JavaSE-11.profile docker tag sapsailing:${release} docker.sapsailing.com/sapsailing:${release} docker push docker.sapsailing.com/sapsailing:${release} if [ "$SET_LATEST" = "1" ]; then diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SecuredDomainType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SecuredDomainType.java index f812bbdb880..920f7ce8d28 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SecuredDomainType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SecuredDomainType.java @@ -101,7 +101,19 @@ public class SecuredDomainType extends HasPermissionsImpl { "SWISS_TIMING_ARCHIVE_ACCOUNT"); public static final HasPermissions TRACTRAC_ACCOUNT = new SecuredDomainType("TRACTRAC_ACCOUNT"); public static final HasPermissions YELLOWBRICK_ACCOUNT = new SecuredDomainType("YELLOWBRICK_ACCOUNT"); + + /** + * The type-relative object identifier is expected to name the "SERVER" object to which the request to read + * or update the wind estimation models is sent. + */ public static final HasPermissions WIND_ESTIMATION_MODELS = new SecuredDomainType("WIND_ESTIMATION_MODELS"); + + /** + * The type-relative object identifier is expected to name the "SERVER" object to which the request to read + * the polar data is sent. + */ + public static final HasPermissions POLAR_DATA = new SecuredDomainType("POLAR_DATA"); + public static final HasPermissions MARK_PROPERTIES = new SecuredDomainType("MARK_PROPERTIES"); public static final HasPermissions MARK_TEMPLATE = new SecuredDomainType("MARK_TEMPLATE"); public static final HasPermissions COURSE_TEMPLATE = new SecuredDomainType("COURSE_TEMPLATE"); diff --git a/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF index 2df4cb54110..05a67a52d04 100644 --- a/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF @@ -37,7 +37,8 @@ Require-Bundle: com.sap.sailing.domain, org.apache.shiro.core;bundle-version="1.2.2", org.hamcrest;bundle-version="2.2.0", net.bytebuddy.byte-buddy;bundle-version="1.12.18", - net.bytebuddy.byte-buddy-agent;bundle-version="1.12.18" + net.bytebuddy.byte-buddy-agent;bundle-version="1.12.18", + com.sap.sse.operationaltransformation Export-Package: com.sap.sailing.domain.test, com.sap.sailing.domain.test.mock Import-Package: org.apache.commons.math;version="2.1.0", diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/tractrac/PasswordSuppressionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/tractrac/PasswordSuppressionTest.java new file mode 100644 index 00000000000..b7053a7bdc4 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/tractrac/PasswordSuppressionTest.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.test.tractrac; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class PasswordSuppressionTest { + @Test + public void testPasswordSuppression() { + assertEquals("https://em.paris1.tractrac.com/update_race_status?eventid=cbdd7370-daea-013c-d54d-342e996a9920&raceid=a90b8e50-e24a-013c-39f7-0eeb2fba6aa7&username=john.doe%40example.com&password=****&race_status=ABORTED", + "https://em.paris1.tractrac.com/update_race_status?eventid=cbdd7370-daea-013c-d54d-342e996a9920&raceid=a90b8e50-e24a-013c-39f7-0eeb2fba6aa7&username=john.doe%40example.com&password=Humba&race_status=ABORTED" + .replaceAll("password=([^&]*)&", "password=****&")); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java index bef550b5fab..6f5bbc71cf2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java @@ -13,6 +13,7 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatchers; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; @@ -23,26 +24,39 @@ import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFlagEventImpl; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPassChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; import com.sap.sailing.domain.base.impl.DynamicBoat; import com.sap.sailing.domain.base.impl.NationalityImpl; import com.sap.sailing.domain.base.impl.PersonImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.impl.RaceAndCompetitorStatusWithRaceLogReconciler; +import com.sap.sailing.server.impl.RaceLogScoringReplicator; +import com.sap.sailing.server.interfaces.RacingEventService; +import com.sap.sailing.server.interfaces.RacingEventServiceOperation; import com.sap.sse.common.Color; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.metering.CPUMeter; import com.tractrac.model.lib.api.event.ICompetitor; import com.tractrac.model.lib.api.event.IRace; import com.tractrac.model.lib.api.event.IRaceCompetitor; @@ -56,6 +70,8 @@ import com.tractrac.model.lib.api.event.RaceStatusType; * */ public class TestTracTracRaceAndCompetitorStatusReconciler { + private static final String TEST_LEADERBOARD_NAME = "test"; + private static final String R1 = "R1"; private AbstractLogEventAuthor author; private TrackedRace trackedRace; private IRace tractracRace; @@ -65,7 +81,7 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { private IRaceCompetitor tractracRaceCompetitor; private ICompetitor tractracCompetitor; private Competitor competitor; - + private FlexibleLeaderboard leaderboard; private static class RaceAndCompetitorStatusWithRaceLogReconcilerWithPublicResultFetcher extends RaceAndCompetitorStatusWithRaceLogReconciler { public RaceAndCompetitorStatusWithRaceLogReconcilerWithPublicResultFetcher(DomainFactory domainFactory, RaceLogResolver raceLogResolver, IRace tractracRace) { @@ -112,7 +128,15 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { /* dateOfBirth */null, "This is Rigo, the coach")), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null, b, /* store */ false); when(tractracCompetitor.getId()).thenReturn((UUID) competitor.getId()); - raceLog = new RaceLogImpl("RaceLogID"); + final RaceDefinition raceDefinition = mock(RaceDefinitionImpl.class); + when(raceDefinition.getCompetitors()).thenReturn(Collections.singleton(competitor)); + when(trackedRace.getRace()).thenReturn(raceDefinition); + final TrackedRegatta trackedRegatta = mock(TrackedRegatta.class); + when(trackedRegatta.getCPUMeter()).thenReturn(CPUMeter.create()); + when(trackedRace.getTrackedRegatta()).thenReturn(trackedRegatta); + leaderboard = new FlexibleLeaderboardImpl(TEST_LEADERBOARD_NAME, new ThresholdBasedResultDiscardingRuleImpl(new int[0]), new LowPoint(), new CourseAreaImpl("area A", new UUID(1200, 1200) , new DegreePosition(100, 100), new MeterDistance(100))); + leaderboard.addRace(trackedRace, R1, /* medalRace */ false); + raceLog = leaderboard.getRacelog(R1, LeaderboardNameConstants.DEFAULT_FLEET_NAME); when(trackedRace.getAttachedRaceLogs()).thenReturn(Collections.singleton(raceLog)); reconciler = new RaceAndCompetitorStatusWithRaceLogReconcilerWithPublicResultFetcher(DomainFactory.INSTANCE, new RaceLogResolver() { @Override @@ -120,6 +144,16 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { return raceLog; } }, tractracRace); + final RacingEventService racingEventService = mock(RacingEventService.class); + when(racingEventService.getLeaderboardByName(TEST_LEADERBOARD_NAME)).thenReturn(leaderboard); + when(racingEventService.getBaseDomainFactory()).thenReturn(com.sap.sailing.domain.base.DomainFactory.INSTANCE); + when(racingEventService.apply(ArgumentMatchers.any(RacingEventServiceOperation.class))).thenAnswer( + invocation -> { + final RacingEventServiceOperation operation = invocation.getArgument(0); + return operation.applyTo(racingEventService); + }); + final RaceLogScoringReplicator raceLogScoringReplicator = new RaceLogScoringReplicator(racingEventService); + leaderboard.getRaceColumnByName(R1).addRaceColumnListener(raceLogScoringReplicator); raceLog.add(new RaceLogPassChangeEventImpl(startOfPass, author, /* pPassId */ 1)); } @@ -344,7 +378,36 @@ public class TestTracTracRaceAndCompetitorStatusReconciler { assertEquals(yetNewerResultTimePoint, raceLogBasedResult.getB()); assertEquals(MaxPointsReason.NONE, raceLogBasedResult.getA().getMaxPointsReason()); assertEquals(0, raceLogBasedResult.getA().getOneBasedRank()); + } + } + + @Test + public void testOfficialNullFinishTimeAndZeroRank() { + final TimePoint resultTimePoint = startOfPass.plus(Duration.ONE_SECOND.times(1)); + when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.NO_DATA); + when(tractracRaceCompetitor.getStatusLastChangedTime()).thenReturn(resultTimePoint.asMillis()); + when(tractracRaceCompetitor.getOfficialRank()).thenReturn(0); + when(trackedRace.getFinishingTime()).thenReturn(null); + reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); + { + // assert scorecorrection msg afterwards is unchanged. + assertNull(leaderboard.getScoreCorrection().getComment()); + assertNull(leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity()); } } + @Test + public void testOfficialFinishTimeAndValidRankUpdateScoreCorrectionMetadata() { + final TimePoint resultTimePoint = startOfPass.plus(Duration.ONE_SECOND.times(1)); + when(tractracRaceCompetitor.getStatus()).thenReturn(RaceCompetitorStatusType.FINISH_CONFIRMED); + when(tractracRaceCompetitor.getStatusLastChangedTime()).thenReturn(resultTimePoint.asMillis()); + when(tractracRaceCompetitor.getOfficialRank()).thenReturn(4); + when(trackedRace.getFinishingTime()).thenReturn(null); + reconciler.reconcileCompetitorStatus(tractracRaceCompetitor, trackedRace); + { + // assert scorecorrection msg afterwards is unchanged. + assertEquals("Results of race "+R1+" have been updated.", leaderboard.getScoreCorrection().getComment()); + assertNotNull(leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity()); + } + } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 004a2820889..e66a166c6f4 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -356,16 +356,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl extends GPSFixTrackImpl""+DynamicGPSFixMovingTrackImpl.this+": computed COG "+speedToNeighbor.getBearing()+" differs significantly (by "+ speedToNeighbor.getBearing().getDifferenceTo(takeCogAndSogFrom.getSpeed().getBearing()).abs()+ - ") from reported COG "+takeCogAndSogFrom.getSpeed().getBearing()); + ") from reported COG "+takeCogAndSogFrom.getSpeed().getBearing()+ + "; time points: "+from.getTimePoint()+" / "+to.getTimePoint()); + } else if (!foundValidPreviousFixInRange) { + logger.finer(()->""+DynamicGPSFixMovingTrackImpl.this+": invalid fix "+to+" due to smoothened speed ratio "+ + getSmoothenedSpeedRatio(speedToNeighbor, takeCogAndSogFrom.getSpeed())+ + " greater or equal threshold "+MAX_SPEED_FACTOR_COMPARED_TO_MEASURED_SPEED_FOR_FILTERING); } numberOfFixesCheckedForOutlier++; return foundValidPreviousFixInRange; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java index 7ebeb69637d..b6099015f1f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/FinishTimeUpdateHandler.java @@ -63,7 +63,7 @@ public class FinishTimeUpdateHandler extends UpdateHandler { final CloseableHttpClient client = HttpClientBuilder.create() .setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()) .build(); - logger.info("Using " + stopTrackingURI.toString() + " to stop tracking"); + logger.info("Using " + eraseSecurityRelatedValuesFromURL(stopTrackingURI.toString()) + " to stop tracking"); final HttpResponse response = client.execute(request); try { parseAndLogResponse(new BufferedReader(new InputStreamReader(response.getEntity().getContent(), diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java index 50b723a4405..9f2bcaf220b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceAbortedHandler.java @@ -40,7 +40,7 @@ public class RaceAbortedHandler extends UpdateHandler implements RaceAbortedList Map additionalArgs = new HashMap<>(); additionalArgs.put("race_status", raceStatus); URL raceAbortedURL = buildUpdateURL(additionalArgs); - logger.info("Using " + raceAbortedURL.toString() + " for the race aborted notification!"); + logger.info("Using " + eraseSecurityRelatedValuesFromURL(raceAbortedURL.toString()) + " for the race aborted notification!"); HttpURLConnection connection = (HttpURLConnection) raceAbortedURL.openConnection(); try { connection = setConnectionProperties(connection); @@ -53,7 +53,8 @@ public class RaceAbortedHandler extends UpdateHandler implements RaceAbortedList if (connection != null) { connection.disconnect(); } else { - logger.severe("Connection to TracTrac race aborted URL " + raceAbortedURL.toString() + " could not be established"); + logger.severe("Connection to TracTrac race aborted URL " + + eraseSecurityRelatedValuesFromURL(raceAbortedURL.toString()) + " could not be established"); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java index 941d74e20ee..68a325c6fe4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeResetHandler.java @@ -29,7 +29,7 @@ public class StartTimeResetHandler extends UpdateHandler implements StartTimeCha return; } URL startTimeUpdateURL = buildUpdateURL(); - logger.info("Using " + startTimeUpdateURL.toString() + " for the start reset!"); + logger.info("Using " + eraseSecurityRelatedValuesFromURL(startTimeUpdateURL.toString()) + " for the start reset!"); HttpURLConnection connection = (HttpURLConnection) startTimeUpdateURL.openConnection(); try { connection = setConnectionProperties(connection); @@ -42,7 +42,8 @@ public class StartTimeResetHandler extends UpdateHandler implements StartTimeCha if (connection != null) { connection.disconnect(); } else { - logger.severe("Connection to TracTrac start time reset URL " + startTimeUpdateURL.toString() + " could not be established"); + logger.severe("Connection to TracTrac start time reset URL " + + eraseSecurityRelatedValuesFromURL(startTimeUpdateURL.toString()) + " could not be established"); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java index db233c01000..fbbca951d2d 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/StartTimeUpdateHandler.java @@ -68,7 +68,7 @@ public class StartTimeUpdateHandler extends UpdateHandler implements StartTimeCh additionalParameters.put(FIELD_RACE_START_TIME, String.valueOf(newStartTime.asMillis())); URL startTimeUpdateURL = buildUpdateURL(additionalParameters); - logger.info("Using " + startTimeUpdateURL.toString() + " for the start time update!"); + logger.info("Using " + eraseSecurityRelatedValuesFromURL(startTimeUpdateURL.toString()) + " for the start time update!"); HttpURLConnection connection = (HttpURLConnection) startTimeUpdateURL.openConnection(); try { connection = setConnectionProperties(connection); @@ -81,7 +81,8 @@ public class StartTimeUpdateHandler extends UpdateHandler implements StartTimeCh if (connection != null) { connection.disconnect(); } else { - logger.severe("Connection to TracTrac Course Update URL " + startTimeUpdateURL.toString() + " could not be established"); + logger.severe("Connection to TracTrac Course Update URL " + + eraseSecurityRelatedValuesFromURL(startTimeUpdateURL.toString()) + " could not be established"); } } if (regatta.isControlTrackingFromStartAndFinishTimes()) { @@ -96,7 +97,7 @@ public class StartTimeUpdateHandler extends UpdateHandler implements StartTimeCh final HttpClient client = HttpClientBuilder.create() .setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()) .build(); - logger.info("Using " + startTrackingURI.toString() + " to start tracking"); + logger.info("Using " + eraseSecurityRelatedValuesFromURL(startTrackingURI.toString()) + " to start tracking"); final HttpResponse response = client.execute(request); try { parseAndLogResponse(new BufferedReader(new InputStreamReader(response.getEntity().getContent(), diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java index 4bf9688e7cc..e66bb304856 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/UpdateHandler.java @@ -91,6 +91,10 @@ public class UpdateHandler { return new URI(baseURI.getScheme(), baseURI.getHost(), baseURI.getPath()+(baseURI.getPath().endsWith("/")?"":"/")+action, baseURI.getFragment()); } + protected String eraseSecurityRelatedValuesFromURL(String url) { + return url.replaceAll("password=([^&]*)&", "password=****&"); + } + /** * @return a new list that the caller may extend to add more parameters; the list returned contains the basic * parameters {@code eventid}, {@code raceid}, {@code username} and {@code password}. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java index 1147f601c48..9e98dc8b263 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java @@ -48,6 +48,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.Notification; @@ -58,7 +59,6 @@ import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DialogUtils; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.security.shared.HasPermissions.DefaultActions; import com.sap.sse.security.shared.dto.SecuredDTO; import com.sap.sse.security.ui.client.UserService; @@ -255,9 +255,9 @@ public abstract class AbstractBoatCertificatesPanel extends SimplePanel { private void formSubmitComplete(SubmitCompleteEvent e) { try { - final JSONObject json = (JSONObject) JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContent(e)); + final JSONObject json = (JSONObject) JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContentFromHtml(e.getResults())); if (json.get(ORCCertificateUploadConstants.CERTIFICATES) != null) { - sailingServiceWrite.getORCCertificates(e.getResults(), new AsyncCallback>() { + sailingServiceWrite.getORCCertificates(json.toString(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { busyIndicator.setBusy(false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java index 51722d4ba4e..5026e638233 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java @@ -23,10 +23,10 @@ import com.sap.sailing.gwt.ui.adminconsole.resulthandling.SensorDataImportResult import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; public abstract class AbstractFileImportWidget extends Composite { @@ -89,7 +89,7 @@ public abstract class AbstractFileImportWidget extends Composite { @UiHandler("formPanelUi") void onFileImportComplete(SubmitCompleteEvent event) { - SensorDataImportResponse importResponse = SensorDataImportResponse.parse(FileUploadUtil.getApplicationJsonContent(event)); + SensorDataImportResponse importResponse = SensorDataImportResponse.parse(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); if (importResponse == null) { Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java index eacd64e7673..51898873145 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java @@ -33,11 +33,11 @@ import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportRe import com.sap.sailing.gwt.ui.client.Displayer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; /** * The UI form to upload data for expedition all in one import. @@ -150,7 +150,7 @@ public class ExpeditionAllInOneImportPanel extends Composite { formPanel.addSubmitCompleteHandler(event -> { validation.run(); busyIndicator.setBusy(false); - final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(FileUploadUtil.getApplicationJsonContent(event)); + final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); if (response == null) { Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else if (response.hasEventId()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java index 4e9f2aa446b..67f72a3b625 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Collection; import java.util.List; +import com.google.gwt.user.client.ui.FocusWidget; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.dto.CourseAreaDTO; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -47,4 +48,9 @@ public class FlexibleLeaderboardEditDialog extends FlexibleLeaderboardDialog { } discardThresholdBoxes = new DiscardThresholdBoxes(this, leaderboard.getDiscardThresholds(), stringMessages); } + + @Override + protected FocusWidget getInitialFocusWidget() { + return displayNameTextBox; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLeaderboardEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLeaderboardEditDialog.java index 50d8fa92d27..f850eaa34b4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLeaderboardEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLeaderboardEditDialog.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Collection; +import com.google.gwt.user.client.ui.FocusWidget; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -22,4 +23,9 @@ public class RegattaLeaderboardEditDialog exte } // else, the regatta leaderboard obtains its result discarding rule implicitly from the underlying regatta adjustVisibilityOfResultDiscardingRuleComponent(); } + + @Override + protected FocusWidget getInitialFocusWidget() { + return displayNameTextBox; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index 277e1328d8c..d4a38bff79e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -69,13 +69,13 @@ import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; import com.sap.sse.gwt.adminconsole.FilterablePanelProvider; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.panels.AbstractFilterablePanel; import com.sap.sse.security.shared.dto.UserDTO; import com.sap.sse.security.ui.client.UserService; @@ -477,7 +477,7 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider raceEntries = windImportResult.getRaceEntries(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java index b744eca3196..99f9b6c0692 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java @@ -41,12 +41,12 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.home.shared.SharedHomeResources; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.fileupload.FileUploadConstants; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.common.media.MediaType; import com.sap.sse.common.media.MimeType; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.media.ImageDTO; import com.sap.sse.gwt.client.media.VideoDTO; import com.sap.sse.gwt.client.panels.HorizontalFlowPanel; @@ -288,7 +288,7 @@ public abstract class AbstractMediaUploadPopup extends DialogBox { // we can get the result text here (see the FormPanel documentation for // further explanation). progressOverlay.setVisible(false); - JSONValue resultJsonValue = JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContent(event)); + JSONValue resultJsonValue = JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); JSONArray resultJson = resultJsonValue.isArray(); boolean uploadSuccessful = false; boolean fileSkipped = false; diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataClientMock.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataClientMock.java index 9907b091ee2..6231c3690b6 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataClientMock.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataClientMock.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import org.json.simple.parser.ParseException; @@ -16,7 +17,7 @@ public class PolarDataClientMock extends PolarDataClient { private final File file; public PolarDataClientMock(File file, ReplicablePolarService polarService, DomainFactory domainFactory) { - super(null, polarService); + super(null, polarService, Optional.empty()); polarService.registerDomainFactory(domainFactory); this.file = file; } diff --git a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index 9ba5c46820c..ad953131191 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -29,13 +29,15 @@ Require-Bundle: com.sap.sse.operationaltransformation, com.sap.sse.security, com.sun.jersey, - org.apache.geronimo.specs.geronimo-jta_1.1_spec; bundle-version="1.1.1", + org.apache.geronimo.specs.geronimo-jta_1.1_spec;bundle-version="1.1.1", org.eclipse.jetty.osgi-servlet-api;bundle-version="3.1.0", org.apache.shiro.core;bundle-version="1.2.2", org.apache.shiro.web;bundle-version="1.2.2", org.apache.shiro.ehcache;bundle-version="1.2.3", com.sun.xml.bind.jaxb-impl;bundle-version="2.3.0", - com.sap.sse.replication.interfaces;bundle-version="1.0.0" + com.sap.sse.replication.interfaces;bundle-version="1.0.0", + org.apache.httpcomponents.httpcore;bundle-version="4.4.9", + com.sap.sailing.shared.server Import-Package: com.sap.sailing.util, javax.ws.rs;version="1.1.1", diff --git a/java/com.sap.sailing.polars/resources/shiro.ini b/java/com.sap.sailing.polars/resources/shiro.ini index bf7d3953556..2a059e2a34f 100644 --- a/java/com.sap.sailing.polars/resources/shiro.ini +++ b/java/com.sap.sailing.polars/resources/shiro.ini @@ -64,11 +64,14 @@ authc.successUrl = /api/restsecurity/hello # authentication with POST parameters "username" and "password". bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter +# The same as bearerToken, but with additional fallback to anonymous authentication. +# In this case sufficient permission checks have to be done on the application side. +bearerTokenOrAnonymous = com.sap.sailing.shared.server.security.BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing + # Specifying filter chains for URL patterns [urls] -# Uncomment the following to require the permission "event:view" in order to use /api/v1/events: -#/api/v1/events = bearerToken +/api/** = bearerTokenOrAnonymous # Example: Secure access to leaderboard race column factor updates; read access should require no permissions #/api/v1/leaderboards/*/racecolumnfactors/** = bearerToken diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/Activator.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/Activator.java index 94c61f5adb7..0702170c4a8 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/Activator.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/Activator.java @@ -3,6 +3,7 @@ package com.sap.sailing.polars.impl; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,6 +27,7 @@ import com.sap.sse.util.ClearStateTestSupport; public class Activator implements BundleActivator { private static final String POLAR_DATA_SOURCE_URL_PROPERTY_NAME = "polardata.source.url"; + private static final String POLAR_DATA_SOURCE_BEARER_TOKEN_PROPERTY_NAME = "polardata.source.bearertoken"; private static final Logger logger = Logger.getLogger(Activator.class.getName()); @@ -42,8 +44,10 @@ public class Activator implements BundleActivator { registrations.add(context.registerService(Replicable.class, service, replicableServiceProperties)); registrations.add(context.registerService(ClearStateTestSupport.class.getName(), service, null)); final String polarDataSourceURL = System.getProperty(POLAR_DATA_SOURCE_URL_PROPERTY_NAME); + final String polarDataBearerToken = System.getProperty(POLAR_DATA_SOURCE_BEARER_TOKEN_PROPERTY_NAME); if (polarDataSourceURL != null && !polarDataSourceURL.isEmpty()) { - waitForRacingEventServiceToObtainDomainFactory(polarDataSourceURL, service, context, polarDataServiceRegistration); + waitForRacingEventServiceToObtainDomainFactory(polarDataSourceURL, + Optional.ofNullable(polarDataBearerToken), service, context, polarDataServiceRegistration); } } @@ -52,7 +56,6 @@ public class Activator implements BundleActivator { * unregisters the service from the OSGi registry because it will temporarily become unusable, runs the polar data * import from the given URL and registers the service again, adding the service registration object to the set of * {@link #registrations}. The domain factory is required to resolve boat classes during de-serialization. - * * @param polarDataServiceRegistration * used to remove the service registration temporarily while updating the service by a remote import * @param registerPolarServiceCallback @@ -60,15 +63,15 @@ public class Activator implements BundleActivator { * {@link Replicable} and as the {@link PolarDataService} with the OSGi registry */ private void waitForRacingEventServiceToObtainDomainFactory(final String polarDataSourceURL, - final ReplicablePolarService polarService, final BundleContext context, - ServiceRegistration polarDataServiceRegistration) { + Optional polarDataBearerToken, final ReplicablePolarService polarService, + final BundleContext context, ServiceRegistration polarDataServiceRegistration) { final Thread t = new Thread(() -> { try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // ensure that classpath:... Shiro ini files are resolved properly logger.info("Waiting for domain factory to be registered with PolarService..."); // Note: although the domainFactory parameter isn't used, using runWithDomainFactory ensures that the domain factory is there polarService.runWithDomainFactory(domainFactory -> { - PolarDataClient polarDataClient = new PolarDataClient(polarDataSourceURL, polarService); + PolarDataClient polarDataClient = new PolarDataClient(polarDataSourceURL, polarService, polarDataBearerToken); try { polarDataServiceRegistration.unregister(); polarDataClient.updatePolarDataRegressions(); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/api/PolarDataResource.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/api/PolarDataResource.java index ebc32f3797a..20fe6b29fde 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/api/PolarDataResource.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/api/PolarDataResource.java @@ -2,6 +2,7 @@ package com.sap.sailing.polars.jaxrs.api; import java.io.IOException; import java.io.OutputStream; +import java.util.logging.Logger; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -10,13 +11,26 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + +import com.sap.sailing.domain.common.security.SecuredDomainType; import com.sap.sailing.polars.jaxrs.AbstractPolarResource; +import com.sap.sse.ServerInfo; +import com.sap.sse.security.shared.HasPermissions.DefaultActions; +import com.sap.sse.security.shared.TypeRelativeObjectIdentifier; @Path("/polar_data") public class PolarDataResource extends AbstractPolarResource { + private static final Logger logger = Logger.getLogger(PolarDataResource.class.getName()); + @GET @Produces("application/octet-stream;charset=UTF-8") public Response getRegressions() throws IOException { + final Subject subject = SecurityUtils.getSubject(); + logger.info("Polar Data requested by "+ (subject.getPrincipal() == null ? "anonymous user" : subject.getPrincipal().toString())); + subject.checkPermission(SecuredDomainType.POLAR_DATA.getStringPermissionForTypeRelativeIdentifier(DefaultActions.READ, + new TypeRelativeObjectIdentifier(ServerInfo.getName()))); return Response.ok(new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/client/PolarDataClient.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/client/PolarDataClient.java index 9402b81b403..ac6a3d65947 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/client/PolarDataClient.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/jaxrs/client/PolarDataClient.java @@ -2,6 +2,7 @@ package com.sap.sailing.polars.jaxrs.client; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -11,9 +12,11 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.json.simple.parser.ParseException; +import com.sap.sailing.domain.common.security.SecuredDomainType; import com.sap.sailing.polars.ReplicablePolarService; import com.sap.sailing.polars.impl.PolarDataServiceImpl; import com.sap.sailing.polars.mining.PolarDataMiner; +import com.sap.sse.security.shared.HasPermissions.DefaultActions; import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes; /** @@ -32,17 +35,25 @@ public class PolarDataClient { private final ReplicablePolarService polarDataService; private final String polarDataSourceURL; + private final Optional polarDataBearerToken; /** * Default constructor is missing because we need {@link PolarDataServiceImpl} to reach regressions + * * @param polarDataSourceURL * archive server URL string * @param polarDataService * {@link PolarDataServiceImpl} service to work with + * @param polarDataBearerToken + * if present, this will be used in a Authorization: Bearer ... HTTP header to authenticate the + * request that fetches the polar data. The token must authenticate a subject with permission + * {@link SecuredDomainType#POLAR_DATA}.{@link DefaultActions#READ READ} on the "SERVER" object from + * where polar data is request as per the {@code polarDataSourceURL}. */ - public PolarDataClient(String polarDataSourceURL, ReplicablePolarService polarDataService) { + public PolarDataClient(String polarDataSourceURL, ReplicablePolarService polarDataService, Optional polarDataBearerToken) { this.polarDataService = polarDataService; this.polarDataSourceURL = polarDataSourceURL; + this.polarDataBearerToken = polarDataBearerToken; } /** @@ -70,9 +81,14 @@ public class PolarDataClient { } protected InputStream getContentFromResponse() throws IOException, ParseException { - HttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()).build(); - HttpGet getProcessor = new HttpGet(getAPIString()); - HttpResponse processorResponse = client.execute(getProcessor); + final HttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()).build(); + final HttpGet getProcessor = new HttpGet(getAPIString()); + polarDataBearerToken.ifPresent(bearerToken -> getProcessor.setHeader("Authorization", "Bearer " + bearerToken)); + final HttpResponse processorResponse = client.execute(getProcessor); + if (processorResponse.getStatusLine().getStatusCode() >= 300) { + throw new IOException("Error trying to load polar data from "+getAPIString()+": " + +processorResponse.getStatusLine().getReasonPhrase()+" ("+processorResponse.getStatusLine().getStatusCode()+")"); + } return processorResponse.getEntity().getContent(); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java index e904d47edf1..294232424e7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java @@ -1,17 +1,59 @@ package com.sap.sailing.server.gateway; +import java.io.IOException; + import javax.servlet.http.HttpServletResponse; +import org.json.simple.JSONAware; + +import com.sap.sse.common.fileupload.FileUploadUtil; + /** - * Base servlet for json exports - * @author Frank + * Base servlet for JSON exports. If implementing subclasses use {@link #setJsonResponseHeader(HttpServletResponse)}, + * they are assumed to write their response to the servlet response's {@link HttpServletResponse#getOutputStream() + * output stream} directly. Alternatively, e.g., when used as the target of a file upload form that technically expects + * HTML output, the output can be set to {@code text/html}, and a special encoding of a JSON response will be used so it + * can be embedded robustly in an HTML document's {@code body} tag and be extracted again by the client receiving the + * event about submission completion. + * + * @author Frank Mittag + * @author Axel Uhl */ @SuppressWarnings("serial") public abstract class AbstractJsonHttpServlet extends SailingServerHttpServlet { + /** + * By invoking this method you declare that you will write the JSON document + * straight to the output stream. + */ protected void setJsonResponseHeader(HttpServletResponse resp) { + setBasicPropertiesInResponseHeader(resp); + resp.setContentType("application/json"); + } + + /** + * By invoking this method you declare that you assume being the target of a form submission + * and will encode your JSON into an HTML document's {@code body} tag, using the + * {@link FileUploadUtil} class. + */ + protected void setJsonEncodedInHtmlResponseHeader(HttpServletResponse resp) { + setBasicPropertiesInResponseHeader(resp); + resp.setContentType("text/html"); + } + + private void setBasicPropertiesInResponseHeader(HttpServletResponse resp) { // to allow access to the json document directly from a client side javascript resp.setHeader("Access-Control-Allow-Origin", "*"); - resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); } + + /** + * Encodes the stringified {@code json} object into an HTML document's {@code body} element, writing the output into + * {@code resp}'s {@link HttpServletResponse#getWriter() writer}. The encoding is done using + * {@link FileUploadUtil#getHtmlWithEmbeddedJsonContent(String)} which the client can + * then also use to {@link FileUploadUtil#getEmbeddedJsonContentFromHtml(String, java.util.function.Function) + * extract} the pure JSON string from the HTML carrier document again. + */ + protected void writeJsonIntoHtmlResponse(HttpServletResponse resp, JSONAware json) throws IOException { + resp.getWriter().write(FileUploadUtil.getHtmlWithEmbeddedJsonContent(json.toJSONString())); + } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java index d3a2f1d62eb..17a7f339ec7 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java @@ -15,9 +15,14 @@ import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; +import com.sap.sse.common.fileupload.FileUploadUtil; /** - * Abstract servlet handling multipart-mime file upload coming from an upload form + * Abstract servlet handling multipart-mime file upload coming from an upload form. The response content type is set to + * "text/html" by default (see {@link #setJsonEncodedInHtmlResponseHeader(HttpServletResponse)}). If your response is a + * JSON document, encode it as an HTML document {@code body} using + * {@link FileUploadUtil#getHtmlWithEmbeddedJsonContent(String)}. A client can then use the submit complete event + * response and decode it using {@link FileUploadUtil#getApplicationJsonContentFromHtml(String)}. * * @author Fredrik Teschke * @author Axel Uhl @@ -56,7 +61,7 @@ public abstract class AbstractFileUploadServlet extends AbstractJsonHttpServlet // Conversely, trying to use text/html as content encoding leads some browsers---especially on mobile devices--- // to do ugly things to the content returned, such as replacing digit sequences by a corresponding element // that allows the user to dial that number with the phone app... - setJsonResponseHeader(resp); + setJsonEncodedInHtmlResponseHeader(resp); process(items, req, resp); } catch (FileUploadException e) { throw new IOException("Could not parse request"); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 940d2326b9e..b7ec14b6087 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -105,7 +105,6 @@ public class FileUploadServlet extends AbstractFileUploadServlet { } resultList.add(result); } - - resultList.writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, resultList); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompareServersResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompareServersResource.java index 5e9cb4cdee0..480133c810c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompareServersResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompareServersResource.java @@ -456,7 +456,9 @@ public class CompareServersResource extends AbstractSailingServerResource { json1.remove(i); json2.remove(item2); } else { - removeDuplicateEntries(item1, item2); + if (item1 != null && item2 != null) { + removeDuplicateEntries(item1, item2); + } i++; } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java index 44b4d84f26a..dd134a180b9 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java @@ -101,7 +101,6 @@ public class ORCCertificateImportServlet extends AbstractFileUploadServlet { private void writeResponse(Iterable certificates, HttpServletResponse resp) throws IOException { // Use text/html to prevent browsers from wrapping the response body, // see "Handling File Upload Responses in GWT" at http://www.artofsolving.com/node/50 - resp.setContentType("text/html;charset=UTF-8"); final JSONObject jsonResponse = new JSONObject(); final ORCCertificateJsonSerializer certificateSerializer = new ORCCertificateJsonSerializer(); final JSONArray certificatesAsJson = new JSONArray(); @@ -110,7 +109,7 @@ public class ORCCertificateImportServlet extends AbstractFileUploadServlet { } jsonResponse.put(ORCCertificateUploadConstants.CERTIFICATES, certificatesAsJson); jsonResponse.put(ORCCertificateUploadConstants.STATUS, "OK"); - jsonResponse.writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, jsonResponse); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java index 5751505cc83..a145dba38b1 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java @@ -130,7 +130,7 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet { importerResult = new ImporterResult(t, Collections.emptyList()); logger.log(Level.SEVERE, t.getMessage()); } finally { - this.toJSON(importerResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, toJSON(importerResult)); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java index 652acec62e5..51377f35008 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java @@ -51,7 +51,7 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet { } catch (Exception e) { importResult.add(e); } finally { - ImportResultSerializer.serializeImportResult(importResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, ImportResultSerializer.serializeImportResult(importResult)); } } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java index d27c7106256..952bd4f6e1f 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java @@ -62,7 +62,7 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet { } catch (Exception e) { importResult.add(e); } finally { - ImportResultSerializer.serializeImportResult(importResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, ImportResultSerializer.serializeImportResult(importResult)); } } } diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889, Polar & Wind estimation Import from 8888).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889, Polar & Wind estimation Import from 8888).launch index b4ec6179d57..3fa50da3a6a 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889, Polar & Wind estimation Import from 8888).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889, Polar & Wind estimation Import from 8888).launch @@ -28,295 +28,291 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index 4084350196d..9e1a9da15f5 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.impl; import java.util.function.Supplier; +import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; @@ -32,7 +33,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; public class RaceLogScoringReplicator implements RaceColumnListenerWithDefaultAction { - + private static final Logger logger = Logger.getLogger(RaceLogScoringReplicator.class.getName()); + private static final long serialVersionUID = -5958519195756937338L; private final RacingEventService service; @@ -54,10 +56,10 @@ public class RaceLogScoringReplicator implements RaceColumnListenerWithDefaultAc } private void handleFinishPositioningList(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent event) { - Leaderboard leaderboard = service.getLeaderboardByName(raceLogIdentifier.getRegattaLikeParent().getName()); + final Leaderboard leaderboard = service.getLeaderboardByName(raceLogIdentifier.getRegattaLikeParent().getName()); if (leaderboard != null) { - Fleet fleet = raceColumn.getFleetByName(raceLogIdentifier.getFleetName()); - RaceLog raceLog = raceColumn.getRaceLog(fleet); + final Fleet fleet = raceColumn.getFleetByName(raceLogIdentifier.getFleetName()); + final RaceLog raceLog = raceColumn.getRaceLog(fleet); checkNeedForScoreCorrectionByResultsOfRaceCommittee(leaderboard, raceColumn, fleet, raceLog, event.getCreatedAt(), event); } } @@ -122,18 +124,34 @@ public class RaceLogScoringReplicator implements RaceColumnListenerWithDefaultAc // RaceLogFinishPositioningConfirmedEvent event was found in the race log } if (positioningList != null) { + boolean metadataUpdateRequired = false; for (CompetitorResult positionedCompetitor : positioningList) { final Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId()); int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor); + final Double preChangeTotalPoints = leaderboard.getTotalPoints(competitor, raceColumn, timePoint); + final MaxPointsReason preChangeMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint); correctScoreInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace, competitor, rankByRaceCommittee, positionedCompetitor.getScore(), positionedCompetitor.getMaxPointsReason()); setMaxPointsReasonInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, positionedCompetitor.getMaxPointsReason(), competitor); + final Double postChangeTotalPoints = leaderboard.getTotalPoints(competitor, raceColumn, timePoint); + final MaxPointsReason postChangeMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint); + if (!metadataUpdateRequired && + (!Util.equalsWithNull(postChangeTotalPoints, preChangeTotalPoints) + || + !Util.equalsWithNull(preChangeMaxPointsReason, postChangeMaxPointsReason))) { + logger.info("Found at least one score correction change in leaderboard "+leaderboard.getName()+ + " for competitor "+competitor.getName()+" in race column "+raceColumn.getName()+ + ". Updating comment and time of last correction."); + metadataUpdateRequired = true; + } } // Update the metadata only if it is newer than any previous score correction update. // This helps if "mechanical" updates are received out of order and will converge towards // the latest update time point: - if (leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null || - (timePoint != null && leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity().before(timePoint))) { + if (metadataUpdateRequired + && (leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null + || (timePoint != null && leaderboard.getScoreCorrection() + .getTimePointOfLastCorrectionsValidity().before(timePoint)))) { // Since the metadata update is used by the Sailing suite to determine the final state of a race, it has to // be triggered, even though no score correction may have been performed String comment = LeaderboardNameConstants.DEFAULT_FLEET_NAME.equals(fleet.getName()) diff --git a/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataClientMock.java b/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataClientMock.java index c1292444471..d8b3b1f4e00 100644 --- a/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataClientMock.java +++ b/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataClientMock.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.util.Map; +import java.util.Optional; import org.json.simple.parser.ParseException; @@ -26,7 +27,7 @@ public class WindEstimationDataClientMock extends WindEstimationDataClient { public WindEstimationDataClientMock(ModelStore modelStore, ReplicableWindEstimationFactoryService windEstimationFactoryService) { - super(null, windEstimationFactoryService); + super(null, windEstimationFactoryService, /* windEstimationModelBearerToken */ Optional.empty()); this.modelStore = modelStore; } diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/Activator.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/Activator.java index d58c813e0e8..2020eb8032a 100644 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/Activator.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/Activator.java @@ -3,6 +3,7 @@ package com.sap.sailing.windestimation.integration; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,6 +53,14 @@ public class Activator implements BundleActivator { * file system that contains serialized model files to load. */ private static final String WIND_ESTIMATION_MODEL_DATA_SOURCE_FOLDER_PROPERTY_NAME = "windestimation.source.folder"; + + /** + * If a system property with this name is provided, the property value will be used as a bearer token in an HTTP + * header Authorization: Bearer ... to authenticate the request fetching the wind estimation data from the + * URL specified in either the {@link #WIND_ESTIMATION_MODEL_DATA_SOURCE_URL_PROPERTY_NAME} or the + * {@link #WIND_ESTIMATION_MODEL_DATA_SOURCE_ALWAYS_URL_PROPERTY_NAME} property. + */ + private static final String WIND_ESTIMATION_MODEL_BEARER_TOKEN_PROPERTY_NAME = "windestimation.source.bearertoken"; private final Set> registrations = new HashSet<>(); @@ -72,6 +81,8 @@ public class Activator implements BundleActivator { .getProperty(WIND_ESTIMATION_MODEL_DATA_SOURCE_ALWAYS_URL_PROPERTY_NAME); final String windEstimationModelDataSourceFolder = System .getProperty(WIND_ESTIMATION_MODEL_DATA_SOURCE_FOLDER_PROPERTY_NAME); + final String windEstimationModelBearerToken = System + .getProperty(WIND_ESTIMATION_MODEL_BEARER_TOKEN_PROPERTY_NAME); int loadFromCount = (windEstimationModelDataSourceURL != null ? 1 : 0) + (windEstimationModelDataSourceAlwaysURL != null ? 1 : 0) + (windEstimationModelDataSourceFolder != null ? 1 : 0); @@ -82,9 +93,9 @@ public class Activator implements BundleActivator { + WIND_ESTIMATION_MODEL_DATA_SOURCE_FOLDER_PROPERTY_NAME + "\""); } if (windEstimationModelDataSourceURL != null && !service.isReady()) { - importWindEstimationModelsFromUrl(windEstimationModelDataSourceURL); + importWindEstimationModelsFromUrl(windEstimationModelDataSourceURL, Optional.ofNullable(windEstimationModelBearerToken)); } else if (windEstimationModelDataSourceAlwaysURL != null) { - importWindEstimationModelsFromUrl(windEstimationModelDataSourceAlwaysURL); + importWindEstimationModelsFromUrl(windEstimationModelDataSourceAlwaysURL, Optional.ofNullable(windEstimationModelBearerToken)); } else if (windEstimationModelDataSourceFolder != null) { importWindEstimationModelsFromFolder(windEstimationModelDataSourceFolder); } @@ -117,10 +128,15 @@ public class Activator implements BundleActivator { } } - private void importWindEstimationModelsFromUrl(String windEstimationModelDataSourceURL) { + /** + * @param windEstimationModelBearerToken + * if present, this bearer token will be used to authenticate the request for the wind estimation model + * data + */ + private void importWindEstimationModelsFromUrl(String windEstimationModelDataSourceURL, Optional windEstimationModelBearerToken) { logger.info("Importing wind estimation data from URL: " + windEstimationModelDataSourceURL); WindEstimationDataClient windEstimationDataClient = new WindEstimationDataClient( - windEstimationModelDataSourceURL, service); + windEstimationModelDataSourceURL, service, windEstimationModelBearerToken); try { windEstimationDataClient.updateWindEstimationModels(); } catch (Exception e) { diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataResource.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataResource.java index bc78f2acb65..3c54ca43b14 100644 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataResource.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/api/WindEstimationDataResource.java @@ -43,6 +43,8 @@ public class WindEstimationDataResource extends AbstractWindEstimationDataResour logger.info("Wind Estimation Model Data requested by "+ (subject.getPrincipal() == null ? "anonymous user" : subject.getPrincipal().toString())); + subject.checkPermission(SecuredDomainType.WIND_ESTIMATION_MODELS.getStringPermissionForTypeRelativeIdentifier( + DefaultActions.READ, new TypeRelativeObjectIdentifier(ServerInfo.getName()))); return Response.ok(new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/client/WindEstimationDataClient.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/client/WindEstimationDataClient.java index 5dbe0914dcd..82f834b4660 100644 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/client/WindEstimationDataClient.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/jaxrs/client/WindEstimationDataClient.java @@ -2,6 +2,7 @@ package com.sap.sailing.windestimation.jaxrs.client; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -29,11 +30,13 @@ public class WindEstimationDataClient { private final ReplicableWindEstimationFactoryService windEstimationFactoryService; private final String windEstimationDataSourceURL; + private final Optional windEstimationModelBearerToken; public WindEstimationDataClient(String windEstimationDataSourceURL, - ReplicableWindEstimationFactoryService windEstimationFactoryService) { + ReplicableWindEstimationFactoryService windEstimationFactoryService, Optional windEstimationModelBearerToken) { this.windEstimationFactoryService = windEstimationFactoryService; this.windEstimationDataSourceURL = windEstimationDataSourceURL; + this.windEstimationModelBearerToken = windEstimationModelBearerToken; } /** @@ -62,9 +65,14 @@ public class WindEstimationDataClient { } protected InputStream getContentFromResponse() throws IOException, ParseException { - HttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()).build(); - HttpGet getProcessor = new HttpGet(getAPIString()); - HttpResponse processorResponse = client.execute(getProcessor); + final HttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()).build(); + final HttpGet getProcessor = new HttpGet(getAPIString()); + windEstimationModelBearerToken.ifPresent(bearerToken -> getProcessor.setHeader("Authorization", "Bearer " + bearerToken)); + final HttpResponse processorResponse = client.execute(getProcessor); + if (processorResponse.getStatusLine().getStatusCode() >= 300) { + throw new IOException("Error trying to load wind estimation data from "+getAPIString()+": " + +processorResponse.getStatusLine().getReasonPhrase()+" ("+processorResponse.getStatusLine().getStatusCode()+")"); + } return processorResponse.getEntity().getContent(); } diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index bbfb35c328c..2dfcfc5e79e 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -23,6 +23,25 @@

Release Notes - Administration Console

+

May 2024

+
    +
  • Reading the wind estimation data from a server during start-up, using the + windestimation.source.url system property, now requires authentication. + The java/target/start script now reads the WINDESTIMATION_BEARER_TOKEN + variable and passes it as the windestimation.source.bearertoken system property + to the application. The user authenticated by that token must have the + WIND_ESTIMATION_MODELS:READ permission for the server identified by the + URL provided in the windestimation.source.url property, for example + WIND_ESTIMATION_MODELS:READ:ARCHIVE if the URL is https://sapsailing.com
  • +
  • Reading the polar data from a server during start-up, using the + polardata.source.url system property, now requires authentication. + The java/target/start script now reads the POLAR_DATA_BEARER_TOKEN + variable and passes it as the polardata.source.bearertoken system property + to the application. The user authenticated by that token must have the + POLAR_DATA:READ permission for the server identified by the + URL provided in the polardata.source.url property, for example + POLAR_DATA:READ:ARCHIVE if the URL is https://sapsailing.com
  • +

April 2024

March 2024