├── .github └── workflows │ ├── README-CRA.md │ ├── README.md │ ├── build-test-push-aws-ecr.yml │ ├── build-workflow.png │ ├── ibm-roks.yml │ ├── java-build-push-git-template.yaml │ ├── pr-analysis.png │ ├── pr-workflow.png │ ├── pr-workflow.yml │ └── toolchain.png ├── .gitignore ├── BUILD.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile-build ├── Dockerfile-lang ├── Dockerfile-tools ├── Dockerfile.basicregistry ├── Jenkinsfile ├── Jenkinsfiledemo ├── LICENSE ├── README.md ├── build_parameters.sh ├── chart └── trader │ ├── Chart.yaml │ ├── bindings.yaml │ ├── templates │ ├── basedeployment.yaml │ ├── deployment.yaml │ ├── hpa.yaml │ ├── istio.yaml │ └── service.yaml │ └── values.yaml ├── cli-config.yml ├── evidence-template └── summary.json ├── lab ├── Lab Intro - Developing and Deploying Microservices into ICP - FINAL.pdf ├── Stock Trader Sample - Diagram.pptx ├── Think Lab 7620 - Developing and Deploying Microservices into ICP.pdf └── stock-trader.png ├── manifest.yml ├── manifests ├── demodeploy.yaml ├── deploy-openshift.yaml ├── deploy.yaml └── trader-values.yaml ├── pipeline-template.yaml ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── ibm │ │ │ └── hybrid │ │ │ └── cloud │ │ │ └── sample │ │ │ └── stocktrader │ │ │ └── trader │ │ │ ├── AddPortfolio.java │ │ │ ├── AddStock.java │ │ │ ├── DisplayMessage.java │ │ │ ├── Error.java │ │ │ ├── Login.java │ │ │ ├── SubmitFeedback.java │ │ │ ├── Summary.java │ │ │ ├── Utilities.java │ │ │ ├── ViewPortfolio.java │ │ │ ├── client │ │ │ ├── BrokerClient.java │ │ │ └── PortfolioServices.old │ │ │ ├── health │ │ │ ├── LivenessProbe.java │ │ │ └── ReadinessProbe.java │ │ │ └── json │ │ │ ├── Broker.java │ │ │ ├── Feedback.java │ │ │ ├── Stock.java │ │ │ └── WatsonInput.java │ ├── liberty │ │ └── config │ │ │ ├── configDropins │ │ │ └── defaults │ │ │ │ └── keystore.xml │ │ │ ├── includes │ │ │ ├── basic.xml │ │ │ ├── ldap.xml │ │ │ ├── none.xml │ │ │ └── oidc.xml │ │ │ ├── jvm.options │ │ │ ├── jvmbx.options │ │ │ ├── resources │ │ │ └── security │ │ │ │ ├── key.p12 │ │ │ │ └── trust.p12 │ │ │ └── server.xml │ ├── resources │ │ └── index.html │ └── webapp │ │ ├── WEB-INF │ │ ├── beans.xml │ │ ├── ibm-web-ext.xml │ │ ├── jsps │ │ │ ├── addPortfolio.jsp │ │ │ ├── addStock.jsp │ │ │ ├── error.jsp │ │ │ ├── login.jsp │ │ │ ├── message.jsp │ │ │ ├── submitFeedback.jsp │ │ │ ├── summary.jsp │ │ │ └── viewPortfolio.jsp │ │ ├── web.xml │ │ └── web.xml.basicregistry │ │ ├── footer.jpg │ │ ├── header.jpg │ │ └── index.html └── test │ └── java │ └── com │ └── ibm │ └── hybrid │ └── cloud │ └── sample │ └── stocktrader │ └── trader │ └── test │ ├── HealthEndpointIT.java │ └── HomePageIT.java └── trigger.txt /.github/workflows/README-CRA.md: -------------------------------------------------------------------------------- 1 | # This folder contains GitHub Actions workflows 2 | 3 | Workflows are used to build and deploy `trader` service to OpenShift cluster. 4 | They are also using *Code Risk Analyzer* and *Security Compliance* services available in IBM Cloud. 5 | 6 | This document describes neccessary steps that must be done before running the workflows. 7 | 8 | Prerequisities: 9 | - IBM Cloud account 10 | - Toolchain in IBM DevOps 11 | - git repo for storing 'evidence' 12 | 13 | Detailed instructions how to set these up is in coresponding sections. 14 | 15 | 16 | # Workflows descriptions 17 | There are 2 workflows defined in this repo: 18 | - Run Code Risk Analyzer on PR workflow - in pr-workflow.yaml file 19 | - Build and Deploy to ROKS workflow - in ibm-roks.yaml file 20 | 21 | ## Run Code Risk Analyzer on PR workflow 22 | This workflow is triggered when developer creates new pull request with the changes that he wants to add to master branch. 23 | ![PR workflow](pr-workflow.png) 24 | Workflow is using [IBM Code Risk Analyzer](https://www.ibm.com/cloud/blog/announcements/find-source-code-vulnerabilities-with-code-risk-analyzer) service to scan source code, and find vulnerabilities in particular: 25 | - In your application dependencies (Python, Node.js, Java) 26 | - In application containers - it scans images using static analysis without a need to run the container 27 | - In your Kubernetes deployment files based on industry standards and community best practices 28 | - It also generates a Bill-of-Materials (BoM) accounting for all the dependencies and their sources for your application and stores in the IBM Cloud DevOps Insights service 29 | 30 | 31 | Result from the analysis are added as pull request comments allowing to make judgment decision whether change can be merged to the master branch. 32 | 33 | ![PR analysis](pr-analysis.png) 34 | 35 | ## Build and Deploy to ROKS workflow 36 | This workflow is triggered when change is committed to the master branch (either directly or via merge of the pull request). 37 | ![Build workflow](build-workflow.png) 38 | Primary function of this workflow is to build and deploy service, in addition it runs VA scan, CRA checks and publishes evidences of these steps to the 'evidence repo'. 39 | 40 | Steps in the workflow: 41 | - build application 42 | - build docker image and publish to IBM Container Registry 43 | - perform VA scan 44 | - perform CRA checks 45 | - publish CRA checks evidences 46 | - deploy application to OCP cluster 47 | 48 | # Detailed setup guide 49 | This section contains all required steps to configure and run above workflows. 50 | 51 | ## Fork trader repo 52 | Fork trader repository to your github account. 53 | 54 | ## Create repo for evidences 55 | Create new repository where the evidences will be stored. This repo will be read by the SCC integration tool during security scans. 56 | When creating, initialize repo with README file. 57 | 58 | ## Setup toolchain in IBM Cloud 59 | 60 | For now, to integrate your GitHub Actions workflow with IBM SCC and IBM CRA you need toolchain defined in the IBM DevOps service. 61 | Login to IBM Cloud – https://cloud,ibm.com 62 | 63 | ### Check for Continuous Deliver service 64 | IBM Toolchains rely on Continuous Delivery service. You need to have one configured on your account that is located in Dallas. To check your services access Resource list from the ‘hamburger menu’ , and type “Continuous” in filter as shown below (verify that location is Dallas) 65 | 66 | If you don’t have service or it is not in Dallas create new one. 67 | 68 | ### Create Continuous Deliver service 69 | In the Resource list page click `Create resource +` button. 70 | In the Catalog page, in search filter start typing `Contin..` and hit enter to limit displayed services. 71 | Click Continuous Delivery tile. 72 | Create the service, select Dallas location and Lite (free) plan. 73 | Click Create button. 74 | 75 | ### Create Toolchain 76 | Once the Continuous Delivery service is created you can create the toolchain. 77 | From ‘hamburger’ menu select DevOps. 78 | Select `Dallas` location and click `Create toolchain` 79 | From template list select “Build your own toolchain”, give a name for chain and click Create 80 | 81 | ### Build Toolchain 82 | Once the toolchain is created you need to add integration tools to it. 83 | 84 | Click `Add tool + ` button, to add following tools: 85 | 1. GitHub – this will represent our evidence repo 86 | Provide parameters for the repository integration: 87 | - Existing repository 88 | - Your evidence repository url 89 | - Uncheck Issues and Tracking 90 | 91 | After you click “Create Integration” button you might be asked to provide authorization for GitHub repo, if GitHub is not already authorized in your IBM Cloud account. 92 | 93 | 2. DevOps Insights – this tool is required for integration with Code Risk Analysis tool. There is no additional parameters for this tool. 94 | 95 | 3. Security and Compliance – this tool is required for integration with IBM Security Compliance Center 96 | Provide: 97 | - Name for the tool – e.g. scc-integration 98 | - Evidence repo URL – e.g. https://github.com/gasgithub/trader-evidence 99 | 100 | Here is how your toolchain should look like: 101 | ![toolchain](toolchain.png) 102 | 103 | Click `Details` link and make a note of your: 104 | - toolchain ID – in this case it is 9bc7232c-25b7-4c82-8df9-2c0250f2b12e 105 | - CRN - crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:9bc7232c-25b7-4c82-8df9-2c0250f2b12e:: 106 | 107 | 108 | ## Configure GitHub Actions workflows 109 | Configure the following parameters (secrets) in your cloned trader repository. To create secrets, select “Settings” tab in your repo and click “Secrets” in the left menu. 110 | Use “New repository secrets” button to create following secrets: 111 | - EVIDENCE_REPO – URL to evidence repository 112 | - PUSH_TOKEN – token to access evidence repository 113 | - IBM_CLOUD_API_KEY – your API KEY 114 | - ICR_NAMESPACE – your namespace in the IBM Container Registry service 115 | - IKS_CLUSTER – name of your OpenShift cluster in IBM Cloud 116 | - TOOLCHAIN_CRN – toolchain crn got from previous step 117 | - TOOLCHAIN_ID – toolchain id got from previous step 118 | 119 | ## Configure Security Compliance Center in IBM Cloud 120 | Detailed SCC configuration is out of scope of this document, please refer to other guide which describes that in more details. 121 | Here we only mention settings relevant to the toolchain integration. 122 | 123 | 1. Scope - Inventory 124 | Make sure your scope configuration includes “Developer Tools” 125 | 126 | 2. Scope – credentials 127 | Make sure that credential that is used during the scan has proper Git authorizations. 128 | To check Git authorizations, log in to IBM Cloud using configured credential and access this link: https://cloud.ibm.com/devops/git?env_id=ibm:yp:us-south 129 | 130 | If you don’t have authorization for GitHub, EDIT the following link with your github userid and repo: https://cloud.ibm.com/devops/setup/deploy?repository=https%3A%2F%2Fgithub.com%YOUR_GIT_ID%2FYOUR_EVIDENCE_REPO_NAME&env_id=ibm%3Ayp%3Aus-south&source_provider=githubconsolidated 131 | 132 | 133 | 3. Profile 134 | Ensure that Continuous Compliance goals are included in your scan profile. 135 | 136 | # Test your configuration 137 | In this section you will test the whole configuration 138 | 139 | ## Test Run Code Risk Analyzer on PR workflow 140 | To test PR workflow you need to create a pull request. 141 | 142 | Modify any file in your forked trader repository, for example README.md, when saving changes instead of committing directly to master branch create a new branch and pull request. You can customize branch name (pr-workflow-test) or accept the default generated name. 143 | 144 | Click “Propose changes” and click “Create pull request” on the next page. 145 | 146 | Once you create pull request, the pull request page is opened and you can see checks being done. 147 | 148 | Meanwhile you can open Actions page and observe your workflow progressing. 149 | 150 | Once the workflow is finished you can check back pull request for comments added by the Code Risk Analyzer tool. 151 | 152 | Your pull request workflow was successfully executed. 153 | 154 | Click “Merge pull request” to merge the changes to the master branch and start build and deploy workflow 155 | 156 | ## Test Build and Deploy to ROKS workflow 157 | If you followed this guide and first tested PR workflow your build and deploy workflow should be already started and running as a result of the merge you did. 158 | 159 | If you didn’t create a pull request, you can just edit any file (for example README.md) and this time commit directly to the master branch. This will trigger build and deploy workflow also. 160 | 161 | Go to the Actions page to follow progress of the workflow. Check if “Publish evidence…” job ended successfully, as it publishes to your evidence repo. 162 | 163 | Verify that summary.json was created in your evidence repo. It will be located in the /raw/cd/unique-id-number folder (look at the latest modification time). 164 | Your workflow executed successfully and generated evidence for SCC. 165 | 166 | # Invoke SCC scan to see data generated by workflow 167 | Login to IBM Cloud and select “Security and Compliance” from hamburger menu. 168 | 169 | Click Configure > Scope > your-scope-name to access scope settings page. 170 | 171 | From Actions menu select Scan, and create new Validation scan, selecting your configured profile. 172 | Once scan is finished access its results via Access > Scans > your-scan-your-profile. 173 | Switch to Resource tab and click your toolchain name. 174 | You will see configured goals crossed with gathered evidences, with green (success), red (failed), yellow (no evidence) information about your workflow execution. 175 | 176 | Great work! You successfully configured and tested GitHub Actions workflow that works with IBM Cloud Code Risk Analyzer and Security and Compliance services. 177 | 178 | 179 | # Appendix - Disabling/enabling workflows 180 | Disabling a workflow allows you to stop a workflow from being triggered without having to delete the file from the repo. 181 | Temporarily disabling a workflow can be useful in some scenarios, for example: 182 | 183 | - A workflow error that produces too many or wrong requests, impacting external services negatively. 184 | - A workflow that is not critical and is consuming too many minutes on your account. 185 | - A workflow that sends requests to a service that is down. 186 | 187 | To disable workflow go to `Actions`, select given workflow, click `...` menu and click `Disable workflow`. 188 | In similar way you can reenable the workflow later. 189 | 190 | For more details check - [Disabling and enabling a workflow](https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow) 191 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # This folder contains GitHub Actions workflows 2 | 3 | Workflows are used to build `trader` service. 4 | For IBM Code Risk Analyzer workflows description and configuration check [README-CRA](README-CRA.md) 5 | 6 | This file describes simple workflow that is used to compile app, build image and publish it to registry (Quay). 7 | 8 | Workflow is defined in the [java-build-push-git-template.yaml](java-build-push-git-template.yaml) file. 9 | 10 | Copy that file to other microservices and change the following settings in the `env` section of the workflow file: 11 | ``` 12 | # EDIT secrets with with your registry, registry path, and apikey 13 | REGISTRY: quay.io 14 | REGISTRY_NAMESPACE: gas_stocktrader 15 | REGISTRY_USER: yourUSER 16 | REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} 17 | IMAGE_NAME: trader 18 | 19 | # GITOPS REGISTRY PARAMS 20 | GITOPS_REPO: stocktrader-ops/stocktrader-gitops 21 | GITOPS_DIR: application 22 | GITOPS_USERNAME: ${{ secrets.GITOPS_USERNAME }} 23 | GITOPS_TOKEN: ${{ secrets.GITOPS_TOKEN }} 24 | ``` 25 | 26 | Gitops registry is where the StockTrader custom resource file is stored. 27 | Workflow updates that file with the new image location and tag. 28 | 29 | Additionally you need to configure following Secrets in your application git repo: 30 | 31 | ``` 32 | REGISTRY_PASSWORD - password/token to image registry that allows write 33 | GITOPS_USERNAME - username for gitops repo 34 | GITOPS_TOKEN - token for gitops repo 35 | ``` 36 | 37 | For sample gitops repo and workflow that is used to deploy app check - https://github.com/stocktrader-ops/stocktrader-gitops 38 | 39 | ## Disable other workflows 40 | As this repo contains also CRA related workflows, disable them, or remove completely, if you are only interested in basic functionality. 41 | 42 | To disable workflow go to `Actions`, select given workflow, click `...` menu and click `Disable workflow`. 43 | In similar way you can reenable the workflow later. -------------------------------------------------------------------------------- /.github/workflows/build-test-push-aws-ecr.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a new container image to Amazon ECR, 2 | # and then will deploy a new task definition to Amazon ECS, when a release is created 3 | # 4 | # To use this workflow, you will need to complete the following set-up steps: 5 | # 6 | # 1. Create an ECR repository to store your images. 7 | # For example: `aws ecr create-repository --repository-name ibmstocktrader/trader --region us-east-2`. 8 | # Replace the value of `ECR_REPOSITORY` in the workflow below with your repository's name. 9 | # Replace the value of `aws-region` in the workflow below with your repository's region. 10 | # 11 | # 2. Create an ECS task definition, an ECS cluster, and an ECS service. 12 | # For example, follow the Getting Started guide on the ECS console: 13 | # https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun 14 | # Replace the values for `service` and `cluster` in the workflow below with your service and cluster names. 15 | # 16 | # 3. Store your ECS task definition as a JSON file in your repository. 17 | # The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`. 18 | # Replace the value of `task-definition` in the workflow below with your JSON file's name. 19 | # Replace the value of `container-name` in the workflow below with the name of the container 20 | # in the `containerDefinitions` section of the task definition. 21 | # 22 | # 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 23 | # See the documentation for each action used below for the recommended IAM policies for this IAM user, 24 | # and best practices on handling the access key credentials. 25 | 26 | name: Build, Push to Amazon ECR, Gitops 27 | 28 | on: 29 | push: 30 | branches: 31 | - master 32 | paths-ignore: 33 | - '.github/**' 34 | release: 35 | types: [created] 36 | 37 | # Environment variables available to all jobs and steps in this workflow 38 | env: 39 | # EDIT secrets with with your registry path, and apikey 40 | # REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE }} 41 | # EDIT with your registry username. 42 | # IMAGE_NAME: portfolio 43 | 44 | GITHUB_SHA: ${{ github.sha }} 45 | 46 | # GITOPS_REPO: IBMStockTrader/stocktrader-gitops 47 | # GITOPS_DIR: application 48 | # GITOPS_USERNAME: ${{ secrets.GITOPS_USERNAME }} 49 | # GITOPS_TOKEN: ${{ secrets.GITOPS_TOKEN }} 50 | 51 | 52 | jobs: 53 | setup-build-publish-deploy: 54 | name: Setup, Build, Publish Deploy 55 | runs-on: ubuntu-latest 56 | environment: production 57 | 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v2 61 | 62 | # Setup java 63 | - name: Setup Java 64 | uses: actions/setup-java@v1 65 | with: 66 | java-version: 17 67 | 68 | # Build and package app 69 | - name: Build and package app 70 | id: unit-test 71 | run: | 72 | mvn clean package 73 | # verify 74 | # cat target/failsafe-reports/failsafe-summary.xml 75 | # grep -q "0" target/failsafe-reports/failsafe-summary.xml 76 | # code=$? 77 | # echo "ret: $code" 78 | # if [[ $code -eq 0 ]]; then 79 | # echo "success" 80 | # echo '::set-output name=unit-test-result::success' 81 | # else 82 | # echo "failed" 83 | # echo '::set-output name=unit-test-result::failed' 84 | # fi 85 | echo '::set-output name=unit-test-result::success' 86 | 87 | - name: Configure AWS credentials 88 | uses: aws-actions/configure-aws-credentials@v1 89 | with: 90 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 91 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 92 | aws-region: us-east-2 93 | 94 | - name: Login to Amazon ECR 95 | id: login-ecr 96 | uses: aws-actions/amazon-ecr-login@v1 97 | 98 | - name: Build, tag, and push image to Amazon ECR 99 | id: build-image 100 | env: 101 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 102 | ECR_REPOSITORY: ibmstocktrader/trader 103 | IMAGE_TAG: ${{ github.sha }} 104 | run: | 105 | # Build a docker container and 106 | # push it to ECR so that it can 107 | # be deployed to ECS. 108 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \ 109 | --build-arg GITHUB_SHA="$GITHUB_SHA" \ 110 | --build-arg GITHUB_REF="$GITHUB_REF" . 111 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 112 | echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" 113 | 114 | update-gitops-repo: 115 | name: Publish image updates to gitops repo 116 | runs-on: ubuntu-latest 117 | needs: [setup-build-publish-deploy] 118 | steps: 119 | 120 | # Checkout gitops repo 121 | - name: Checkout gitops repo 122 | uses: actions/checkout@v2 123 | with: 124 | repository: ${{env.GITOPS_REPO}} 125 | path: gitops 126 | token: ${{secrets.GITOPS_TOKEN}} 127 | 128 | # Update application 129 | - name: Upate application 130 | run: | 131 | set -x 132 | set +e 133 | ls -la 134 | ls -la gitops 135 | cd gitops 136 | 137 | ## update manifests to new image and tag 138 | APP_IMAGE="$ECR_REGISTRY/$ECR_REPOSITORY" 139 | VERSION="$GITHUB_SHA" 140 | echo "image-registry-path: ${{needs.setup-build-publish-deploy.image-registry-path}}" 141 | echo "${APP_IMAGE}" 142 | echo "${VERSION}" 143 | echo "print yq version" 144 | yq --version 145 | # yq w -i "${GITOPS_DIR}/stocktrader-aws-eks-cr.yml" spec.trader.image.repository "${APP_IMAGE}" 146 | yq e ".spec.trader.image.repository = \"$APP_IMAGE\"" -i "${GITOPS_DIR}/stocktrader-aws-eks-cr.yml" 147 | # yq w -i "${GITOPS_DIR}/stocktrader-aws-eks-cr.yml" spec.trader.image.tag "${VERSION}" 148 | yq e ".spec.trader.image.tag = \"$VERSION\"" -i "${GITOPS_DIR}/stocktrader-aws-eks-cr.yml" 149 | cat "${GITOPS_DIR}/stocktrader-aws-eks-cr.yml" 150 | if [[ $(git status -s | wc -l) -eq 0 ]]; then 151 | echo "No changes" 152 | exit 0 153 | fi 154 | git add "${GITOPS_DIR}/" 155 | git config --global user.name 'GH Actions' 156 | git config --global user.email 'github-actions@users.noreply.github.com' 157 | git commit -am "Updates ${APP_NAME} to ${VERSION}" 158 | git push https://$GITOPS_USERNAME:$GITOPS_TOKEN@github.com/$GITOPS_REPO 159 | -------------------------------------------------------------------------------- /.github/workflows/build-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/.github/workflows/build-workflow.png -------------------------------------------------------------------------------- /.github/workflows/java-build-push-git-template.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will: 2 | # - build a maven Java application 3 | # - create a docker container 4 | # - publish it to Quay 5 | # - commit updates to gitops repo 6 | # 7 | ### Before you begin: 8 | # - Have write access to a container image registry such as quay.io or Dockerhub. 9 | # - Have access to your gitops repo. 10 | 11 | # Name of the workflow 12 | name: Build, Push, Gitops 13 | 14 | on: 15 | push: 16 | branches: 17 | - master 18 | paths-ignore: 19 | - '.github/**' 20 | 21 | 22 | # Environment variables available to all jobs and steps in this workflow 23 | env: 24 | # EDIT secrets with with your registry, registry path, and apikey 25 | REGISTRY: quay.io 26 | REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE }} 27 | # EDIT with your registry username. 28 | REGISTRY_USER: ${{ secrets.REGISTRY_USER }} 29 | REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} 30 | IMAGE_NAME: trader 31 | 32 | GITHUB_SHA: ${{ github.sha }} 33 | 34 | GITOPS_REPO: stocktrader-ops/stocktrader-gitops 35 | GITOPS_DIR: application 36 | GITOPS_USERNAME: ${{ secrets.GITOPS_USERNAME }} 37 | GITOPS_TOKEN: ${{ secrets.GITOPS_TOKEN }} 38 | 39 | jobs: 40 | setup-build-publish-deploy: 41 | name: Setup, Build, Publish 42 | runs-on: ubuntu-latest 43 | outputs: 44 | image-registry-path: ${{ steps.push-to-registry.outputs.image-registry-path }} 45 | unit-test-result: ${{ steps.unit-test.outputs.unit-test-result }} 46 | environment: production 47 | steps: 48 | 49 | # Checkout app repo 50 | - name: Checkout 51 | uses: actions/checkout@v2 52 | 53 | 54 | # Setup java 55 | - name: Setup Java 56 | uses: actions/setup-java@v1 57 | with: 58 | java-version: 17 59 | 60 | # Build and package app 61 | - name: Build and package app 62 | id: unit-test 63 | run: | 64 | mvn clean verify 65 | cat target/failsafe-reports/failsafe-summary.xml 66 | grep -q "0" target/failsafe-reports/failsafe-summary.xml 67 | code=$? 68 | echo "ret: $code" 69 | if [[ $code -eq 0 ]]; then 70 | echo "success" 71 | echo '::set-output name=unit-test-result::success' 72 | else 73 | echo "failed" 74 | echo '::set-output name=unit-test-result::failed' 75 | fi 76 | 77 | # Build the Docker image 78 | - name: Build with Docker 79 | run: | 80 | docker build -t "$REGISTRY"/"$REGISTRY_NAMESPACE"/"$IMAGE_NAME":"$GITHUB_SHA" \ 81 | --build-arg GITHUB_SHA="$GITHUB_SHA" \ 82 | --build-arg GITHUB_REF="$GITHUB_REF" . 83 | 84 | # Push the image to Image Registry 85 | - name: Push the image to Registry 86 | id: push-to-registry 87 | run: | 88 | docker login -u="$REGISTRY_USER" -p="$REGISTRY_PASSWORD" "$REGISTRY" 89 | docker push $REGISTRY/$REGISTRY_NAMESPACE/$IMAGE_NAME:$GITHUB_SHA 90 | echo '::set-output name=image-registry-path::$REGISTRY_HOSTNAME/$REGISTRY_NAMESPACE/$IMAGE_NAME:$GITHUB_SHA' 91 | 92 | update-gitops-repo: 93 | name: Publish image updates to gitops repo 94 | runs-on: ubuntu-latest 95 | needs: [setup-build-publish-deploy] 96 | steps: 97 | 98 | # Checkout gitops repo 99 | - name: Checkout gitops repo 100 | uses: actions/checkout@v2 101 | with: 102 | repository: ${{env.GITOPS_REPO}} 103 | path: gitops 104 | token: ${{secrets.GITOPS_TOKEN}} 105 | 106 | # Update application 107 | - name: Upate application 108 | run: | 109 | set -x 110 | set +e 111 | ls -la 112 | ls -la gitops 113 | cd gitops 114 | 115 | ## update manifests to new image and tag 116 | APP_IMAGE="$REGISTRY/$REGISTRY_NAMESPACE/$IMAGE_NAME" 117 | VERSION="$GITHUB_SHA" 118 | echo "image-registry-path: ${{needs.setup-build-publish-deploy.image-registry-path}}" 119 | echo "${APP_IMAGE}" 120 | echo "${VERSION}" 121 | 122 | echo "print yq version" 123 | yq --version 124 | # yq w -i "${GITOPS_DIR}/stocktrader-cr.yaml" spec.trader.image.repository "${APP_IMAGE}" 125 | yq e ".spec.trader.image.repository = \"$APP_IMAGE\"" -i "${GITOPS_DIR}/stocktrader-cr.yaml" 126 | 127 | # yq w -i "${GITOPS_DIR}/stocktrader-cr.yaml" spec.trader.image.tag "${VERSION}" 128 | yq e ".spec.trader.image.tag = \"$VERSION\"" -i "${GITOPS_DIR}/stocktrader-cr.yaml" 129 | 130 | cat "${GITOPS_DIR}/stocktrader-cr.yaml" 131 | 132 | if [[ $(git status -s | wc -l) -eq 0 ]]; then 133 | echo "No changes" 134 | exit 0 135 | fi 136 | git add "${GITOPS_DIR}/" 137 | git config --global user.name 'GH Actions' 138 | git config --global user.email 'github-actions@users.noreply.github.com' 139 | git commit -am "Updates ${APP_NAME} to ${VERSION}" 140 | git push https://$GITOPS_USERNAME:$GITOPS_TOKEN@github.com/$GITOPS_REPO 141 | 142 | 143 | -------------------------------------------------------------------------------- /.github/workflows/pr-analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/.github/workflows/pr-analysis.png -------------------------------------------------------------------------------- /.github/workflows/pr-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/.github/workflows/pr-workflow.png -------------------------------------------------------------------------------- /.github/workflows/toolchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/.github/workflows/toolchain.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | *.class 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Mobile Tools for Java (J2ME) 21 | .mtj.tmp/ 22 | 23 | # Package Files # 24 | *.jar 25 | *.war 26 | *.ear 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | 32 | .gradle 33 | /build/ 34 | 35 | # Ignore Gradle GUI config 36 | gradle-app.setting 37 | 38 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 39 | !gradle-wrapper.jar 40 | 41 | # Cache of project 42 | .gradletasknamecache 43 | 44 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 45 | # gradle/wrapper/gradle-wrapper.properties 46 | 47 | 48 | .metadata 49 | bin/ 50 | tmp/ 51 | *.tmp 52 | *.bak 53 | *.swp 54 | *~.nib 55 | local.properties 56 | .settings/ 57 | .loadpath 58 | .recommenders 59 | 60 | # Eclipse Core 61 | .project 62 | 63 | # External tool builders 64 | .externalToolBuilders/ 65 | 66 | # Locally stored "Eclipse launch configurations" 67 | *.launch 68 | 69 | # PyDev specific (Python IDE for Eclipse) 70 | *.pydevproject 71 | 72 | # CDT-specific (C/C++ Development Tooling) 73 | .cproject 74 | 75 | # JDT-specific (Eclipse Java Development Tools) 76 | .classpath 77 | 78 | # Java annotation processor (APT) 79 | .factorypath 80 | 81 | # PDT-specific (PHP Development Tools) 82 | .buildpath 83 | 84 | # sbteclipse plugin 85 | .target 86 | 87 | # Tern plugin 88 | .tern-project 89 | 90 | # TeXlipse plugin 91 | .texlipse 92 | 93 | # STS (Spring Tool Suite) 94 | .springBeans 95 | 96 | # Code Recommenders 97 | .recommenders/ 98 | 99 | #RTC 100 | .jazz5 101 | clientdb.xml 102 | .cache-main 103 | 104 | target/ 105 | 106 | .gradle/ 107 | build/ 108 | 109 | # Microclimate 110 | mc-target 111 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | # Pipeline install 18 | * Select the OCP project you want to use this in using `oc project ` e.g. `oc project stock-trader` 19 | * Run `oc apply -f pipeline-template.yaml` 20 | * Run `oc new-app --template=stocktrader-trader-pipeline -p GIT_SOURCE_URL=https://github.com/IBMStockTrader/trader.git -p GIT_SOURCE_REF=master` 21 | * Install the trader app using the yaml from `manifests/deploy-openshift.yaml` which includes a `DeploymentConfig` 22 | * Change the `namespace` field in line 22 and the namespace in the path of the Docker registry in line 36 of the file to the namespace where your `ImageStream` is deployed to 23 | * You should now have a BuildConfig called `stocktrader-trader` and a ImageStream in your OCP project 24 | 25 | # Github Webhook for the pipeline 26 | After applying the pipeline file, you have to create a secret. This can be easily done from the CLI for testing purposes: 27 | * execute (repalce by a random string) `oc create secret generic github-webhook --from-literal=WebHookSecretKey=` 28 | * execute `oc describe bc stocktrader-trader` 29 | * note down the webhook url and replace with 30 | * configure a webhook as described here: https://developer.github.com/webhooks/creating/ 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | ## Contributing In General 18 | Our project welcomes external contributions! If you have an itch, please feel free to scratch it. 19 | 20 | To contribute code or documentation, please submit a pull request to the [GitHub repository](https://github.ibm.com/StockTrader/trader). 21 | 22 | A good way to familiarize yourself with the codebase and contribution process is to look for and tackle low-hanging fruit in the [issue tracker](https://github.ibm.com/StockTrader/trader/issues). Before embarking on a more ambitious contribution, please quickly [get in touch](#communication) with us. 23 | 24 | **We appreciate your effort, and want to avoid a situation where a contribution requires extensive rework (by you or by us), sits in the queue for a long time, or cannot be accepted at all!** 25 | 26 | ### Proposing new features 27 | 28 | If you would like to implement a new feature, please [raise an issue](https://github.ibm.com/StockTrader/trader/issues) before sending a pull request so the feature can be discussed. 29 | This is to avoid you spending your valuable time working on a feature that the project developers are not willing to accept into the code base. 30 | 31 | ### Fixing bugs 32 | 33 | If you would like to fix a bug, please [raise an issue](https://github.ibm.com/StockTrader/trader/issues) before sending a pull request so it can be discussed. 34 | If the fix is trivial or non controversial then this is not usually necessary. 35 | 36 | ### Merge approval 37 | 38 | The project maintainers use LGTM (Looks Good To Me) in comments on the code review to 39 | indicate acceptance. A change requires LGTMs from two of the maintainers of each 40 | component affected. 41 | 42 | For more details, see the [MAINTAINERS](MAINTAINERS.md) page. 43 | 44 | ## Communication 45 | Please feel free to connect with us on our [Slack channel](https://ibm-cloud.slack.com). 46 | 47 | ## Setup 48 | Please add any special setup instructions for your project to help the developer become productive quickly. 49 | 50 | ## Testing 51 | Please provide information that helps the developer test any changes they make before submitting. 52 | 53 | ## Coding style guidelines 54 | Beautiful code rocks! Please share any specific style guidelines you might have for your project. 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2021 IBM Corp All Rights Reserved 2 | # Copyright 2022-2025 Kyndryl Corp, All Rights Reserved 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # If building locally, you have to complete a maven build first, before running the Docker build 17 | 18 | # FROM openliberty/open-liberty:21.0.0.9-kernel-slim-java11-openj9-ubi 19 | FROM openliberty/open-liberty:25.0.0.3-full-java17-openj9-ubi 20 | USER root 21 | 22 | COPY --chown=1001:0 src/main/liberty/config /config 23 | 24 | # This script will add the requested XML snippets to enable Liberty features and grow image to be fit-for-purpose using featureUtility. 25 | # Only available in 'kernel-slim'. The 'full' tag already includes all features for convenience. 26 | # RUN features.sh 27 | COPY --chown=1001:0 target/TraderUI.war /config/apps/TraderUI.war 28 | 29 | USER 1001 30 | RUN configure.sh 31 | -------------------------------------------------------------------------------- /Dockerfile-build: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ENV JAVA_VERSION_PREFIX 1.8.0 16 | ENV HOME /home/default 17 | 18 | RUN set -eux; \ 19 | ARCH="$(dpkg --print-architecture)"; \ 20 | case "${ARCH}" in \ 21 | amd64|x86_64) \ 22 | YML_FILE='sdk/linux/x86_64/index.yml'; \ 23 | ;; \ 24 | i386) \ 25 | YML_FILE='sdk/linux/i386/index.yml'; \ 26 | ;; \ 27 | ppc64el|ppc64le) \ 28 | YML_FILE='sdk/linux/ppc64le/index.yml'; \ 29 | ;; \ 30 | s390) \ 31 | YML_FILE='sdk/linux/s390/index.yml'; \ 32 | ;; \ 33 | s390x) \ 34 | YML_FILE='sdk/linux/s390x/index.yml'; \ 35 | ;; \ 36 | *) \ 37 | echo "Unsupported arch: ${ARCH}"; \ 38 | exit 1; \ 39 | ;; \ 40 | esac; \ 41 | BASE_URL="https://public.dhe.ibm.com/ibmdl/export/pub/systems/cloud/runtimes/java/meta/"; \ 42 | wget -q -U UA_IBM_JAVA_Docker -O /tmp/index.yml ${BASE_URL}/${YML_FILE}; \ 43 | ESUM=$(cat /tmp/index.yml | sed -n '/'${JAVA_VERSION_PREFIX}'/{n;n;p}' | sed -n 's/\s*sha256sum:\s//p' | tr -d '\r' | tail -1); \ 44 | JAVA_URL=$(cat /tmp/index.yml | sed -n '/'${JAVA_VERSION_PREFIX}'/{n;p}' | sed -n 's/\s*uri:\s//p' | tr -d '\r' | tail -1); \ 45 | wget -q -U UA_IBM_JAVA_Docker -O /tmp/ibm-java.bin ${JAVA_URL}; \ 46 | echo "${ESUM} /tmp/ibm-java.bin" | sha256sum -c -; \ 47 | echo "INSTALLER_UI=silent" > /tmp/response.properties; \ 48 | echo "USER_INSTALL_DIR=$HOME/java" >> /tmp/response.properties; \ 49 | echo "LICENSE_ACCEPTED=TRUE" >> /tmp/response.properties; \ 50 | mkdir -p $HOME/java; \ 51 | chmod +x /tmp/ibm-java.bin; \ 52 | /tmp/ibm-java.bin -i silent -f /tmp/response.properties; \ 53 | rm -f /tmp/response.properties; \ 54 | rm -f /tmp/index.yml; \ 55 | rm -f /tmp/ibm-java.bin; \ 56 | cd $HOME/java/jre/lib; \ 57 | rm -rf icc; 58 | 59 | RUN mkdir -p $HOME/mvn; \ 60 | MAVEN_VERSION=$(wget -qO- https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/maven-metadata.xml | sed -n 's/\s*\(.*\)<.*>/\1/p'); \ 61 | wget -q -U UA_IBM_JAVA_Docker -O $HOME/mvn/apache-maven-${MAVEN_VERSION}-bin.tar.gz https://search.maven.org/remotecontent?filepath=org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz; \ 62 | tar xf $HOME/mvn/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C $HOME/mvn; \ 63 | mv $HOME/mvn/apache-maven-${MAVEN_VERSION} $HOME/mvn/apache-maven; \ 64 | rm -f $HOME/mvn/apache-maven-${MAVEN_VERSION}-bin.tar.gz; 65 | 66 | RUN mkdir -m 777 -p /config/resources 67 | 68 | ENV JAVA_HOME=$HOME/java \ 69 | PATH=$HOME/java/jre/bin:$HOME/mvn/apache-maven/bin:$PATH 70 | -------------------------------------------------------------------------------- /Dockerfile-lang: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM websphere-liberty:webProfile7 16 | MAINTAINER IBM Java engineering at IBM Cloud 17 | 18 | RUN mkdir -m 777 -p /config/resources 19 | 20 | # Upgrade to production license if URL to JAR provided 21 | ARG LICENSE_JAR_URL 22 | RUN \ 23 | if [ $LICENSE_JAR_URL ]; then \ 24 | wget $LICENSE_JAR_URL -O /tmp/license.jar \ 25 | && java -jar /tmp/license.jar -acceptLicense /opt/ibm \ 26 | && rm /tmp/license.jar; \ 27 | fi 28 | -------------------------------------------------------------------------------- /Dockerfile-tools: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM ibmjava:8-sdk 16 | 17 | MAINTAINER IBM Java Engineering at IBM Cloud 18 | 19 | RUN apt-get update && apt-get install -y maven 20 | 21 | ENV PATH /project/target/liberty/wlp/bin/:$PATH 22 | 23 | ARG bx_dev_user=root 24 | ARG bx_dev_userid=1000 25 | RUN BX_DEV_USER=$bx_dev_user 26 | RUN BX_DEV_USERID=$bx_dev_userid 27 | RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi 28 | -------------------------------------------------------------------------------- /Dockerfile.basicregistry: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM open-liberty:microProfile1 16 | # FROM websphere-liberty:microProfile 17 | COPY server.xml /config/server.xml 18 | COPY jvm.options /config/jvm.options 19 | COPY target/trader-1.0-SNAPSHOT.war /config/apps/TraderUI.war 20 | COPY key.jks /config/resources/security/key.jks 21 | COPY validationKeystore.jks /config/resources/security/validationKeystore.jks 22 | COPY keystore.xml /config/configDropins/defaults/keystore.xml 23 | # COPY ltpa.keys /output/resources/security/ltpa.keys 24 | # RUN installUtility install --acceptLicense defaultServer 25 | RUN apt-get update 26 | RUN apt-get install curl -y 27 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | @Library('MicroserviceBuilder') _ 4 | microserviceBuilderPipeline { 5 | image = 'messaging' 6 | test = 'false' 7 | } 8 | -------------------------------------------------------------------------------- /Jenkinsfiledemo: -------------------------------------------------------------------------------- 1 | //Needed for basic Jenkins 2 | pipeline { 3 | environment { 4 | componentName = "trader" 5 | imagename = "${componentName}:demo" 6 | } 7 | 8 | tools { 9 | maven 'Maven 3.6.0' 10 | jdk 'jdk9' 11 | } 12 | 13 | agent any 14 | // agent { dockerfile true } 15 | 16 | stages { 17 | stage ('Initialize') { 18 | steps { 19 | sh ''' 20 | echo "PATH = ${PATH}" 21 | echo "M2_HOME = ${M2_HOME}" 22 | ''' 23 | } 24 | } 25 | 26 | stage('MVN Build') { 27 | steps { 28 | sh 'mvn clean package' 29 | } 30 | } 31 | 32 | stage('Docker Build&Push') { 33 | steps { 34 | script { 35 | // echo 'Delivering....' 36 | docker.build imagename 37 | } 38 | sh '/pushdocker2icp.sh $imagename' 39 | } 40 | } 41 | 42 | stage('Deploy2ICP') { 43 | steps { 44 | echo 'Deploying....' 45 | sh '/deploy2icp.sh $componentName' 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | The **trader** microservice provides the UI for the *Stock Trader* sample. It calls the **broker** microservice, 18 | which then calls various other services as needed. It uses the *mpRestClient* to make the call, and passes a JWT on the 19 | request, which **broker** checks for via *mpJwt*. 20 | 21 | ![Architecural Diagram](lab/stock-trader.png) 22 | 23 | The main entry point is the **summary** servlet, which lets you choose an operation and a portfolio to act upon. It 24 | transfers control to other servlets, such as **addPortfolio**, **viewPortfolio**, and **addStock**, each of which 25 | transfers control back to **summary** when done. The **viewPortfolio** and **addStock** servlets expect a query param 26 | named *owner*. 27 | 28 | Each page has a header and footer image, and there's an index.html that redirects to the **summary** servlet. 29 | 30 | The servlets just concern themselves with constructing the right **HTML** to return. The UI is very basic; there 31 | is no use of **JavaScript** or anything fancy. All of the real logic is in the PortfolioServices.java, which 32 | contains all of the REST calls to the Broker microservice, and appropriate JSON wrangling. 33 | 34 | You can hit the main entry point by entering a URL such as `http://localhost:9080/trader/summary` in your 35 | browser's address bar. Or in a Kubernetes environment, you'd replace `localhost` with your proxy node address, and 36 | `9080` with your node port or ingress port. You also need to use `https` if using the IBMid version. 37 | 38 | This is version 1 of the *Stock Trader* UI, implemented in **Java**, and is deliberately simplistic. See the 39 | **tradr** sibling repository for an alternate, more professional-looking version, implemented in **JavaScript** and **Vue**. 40 | 41 | ### Prerequisites for Deployment 42 | This project requires two secrets: `jwt` and `oidc`. You create these secrets by running: 43 | ```bash 44 | kubectl create secret generic jwt -n stock-trader --from-literal=audience=stock-trader --from-literal=issuer=http://stock-trader.ibm.com 45 | 46 | kubectl create secret generic oidc -n stock-trader --from-literal=name= --from-literal=issuer= --from-literal=auth= --from-literal=token= --from-literal=id= --from-literal=secret= --from-literal=key= --from-literal=nodeport=https://: 47 | 48 | # Example oidc: 49 | kubectl create secret generic oidc -n stock-trader --from-literal=name=blueLogin --from-literal=issuer=https://iam.toronto.ca.ibm.com --from-literal=auth=https://iam.ibm.com/idaas/oidc/endpoint/default/authorize --from-literal=token=https://iam.ibm.com/idaas/oidc/endpoint/default/token --from-literal=id=N2k3kD3kks9256x3 --from-literal=secret=I33kkj2k330023 --from-literal=key=idaaskey --from-literal=nodeport=https://10.42.95.159:32389 50 | ``` 51 | 52 | You'll also need to enable login to the IBM Cloud Private internal Docker registry by following 53 | [these steps](https://www.ibm.com/support/knowledgecenter/en/SSBS6K_3.1.2/manage_images/configuring_docker_cli.html). 54 | Don't forget to restart Docker after adding your cert. On macOS you can restart Docker by running: 55 | ```bash 56 | osascript -e 'quit app "Docker"' 57 | open -a Docker 58 | ``` 59 | 60 | ### Build and Deploy to ICP 61 | To build `trader` clone this repo and run: 62 | ```bash 63 | mvn package 64 | docker build -t trader . 65 | docker tag trader:latest .icp:8500/stock-trader/trader:latest 66 | docker push .icp:8500/stock-trader/trader:latest 67 | ``` 68 | 69 | Use WebSphere Liberty helm chart to deploy Trader microservice to ICP: 70 | ```bash 71 | helm repo add ibm-charts https://raw.githubusercontent.com/IBM/charts/master/repo/stable/ 72 | helm install ibm-charts/ibm-websphere-liberty -f -n --tls 73 | ``` 74 | 75 | In practice this means you'll run something like: 76 | ```bash 77 | docker build -t trader . 78 | docker tag trader:latest mycluster.icp:8500/stock-trader/trader:latest 79 | docker push mycluster.icp:8500/stock-trader/trader:latest 80 | 81 | helm repo add ibm-charts https://raw.githubusercontent.com/IBM/charts/master/repo/stable/ 82 | helm install ibm-charts/ibm-websphere-liberty -f manifests/trader-values.yaml -n trader --namespace stock-trader --tls 83 | ``` 84 | -------------------------------------------------------------------------------- /build_parameters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | maven_goals="install" 4 | 5 | image_name="trader" 6 | image_tag="latest" 7 | image_namespace="cicd" 8 | 9 | deployment_create="true" 10 | deplyoment_namespace="stocktrader" 11 | 12 | kubectl_apply="services.yaml" 13 | -------------------------------------------------------------------------------- /chart/trader/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: v1 16 | description: A Helm chart for the Trader microservice from IBM Stock Trader 17 | name: trader 18 | version: 1.0.0 19 | -------------------------------------------------------------------------------- /chart/trader/bindings.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file is included in-line in the env section of deployment.yaml 16 | # if .Values.generatedBindings.enabled is true. 17 | - name: IBM_APM_SERVER_URL 18 | valueFrom: 19 | secretKeyRef: 20 | name: apm-server-config 21 | key: ibm_apm_server_url 22 | optional: true 23 | - name: IBM_APM_KEYFILE 24 | valueFrom: 25 | secretKeyRef: 26 | name: apm-server-config 27 | key: ibm_apm_keyfile 28 | optional: true 29 | - name: IBM_APM_KEYFILE_PASSWORD 30 | valueFrom: 31 | secretKeyRef: 32 | name: apm-server-config 33 | key: ibm_apm_keyfile_password 34 | optional: true 35 | - name: IBM_APM_INGRESS_URL 36 | valueFrom: 37 | secretKeyRef: 38 | name: apm-server-config 39 | key: ibm_apm_ingress_url 40 | optional: true 41 | - name: IBM_APM_ACCESS_TOKEN 42 | valueFrom: 43 | secretKeyRef: 44 | name: apm-server-config 45 | key: ibm_apm_access_token 46 | optional: true 47 | -------------------------------------------------------------------------------- /chart/trader/templates/basedeployment.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | {{ if .Values.base.enabled }} 16 | apiVersion: extensions/v1beta1 17 | kind: Deployment 18 | metadata: 19 | name: "{{ .Chart.Name }}-basedeployment" 20 | labels: 21 | chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' 22 | spec: 23 | replicas: {{ .Values.base.replicaCount }} 24 | revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} 25 | template: 26 | metadata: 27 | labels: 28 | app: "{{ .Chart.Name }}-selector" 29 | version: "base" 30 | spec: 31 | containers: 32 | - name: "{{ .Chart.Name }}" 33 | image: "{{ .Values.image.repository }}:{{ .Values.base.image.tag }}" 34 | imagePullPolicy: {{ .Values.image.pullPolicy }} 35 | {{ if .Values.istio.enabled }} 36 | {{ else }} 37 | readinessProbe: 38 | httpGet: 39 | path: /trader/health 40 | port: 9080 41 | initialDelaySeconds: 20 42 | {{ end }} 43 | resources: 44 | requests: 45 | cpu: "{{ .Values.image.resources.requests.cpu }}" 46 | memory: "{{ .Values.image.resources.requests.memory }}" 47 | env: 48 | - name: PORT 49 | value: "{{ .Values.service.servicePort }}" 50 | {{- if .Values.generatedBindings.enabled }} 51 | {{.Files.Get "bindings.yaml" | indent 10 }} 52 | {{- end }} 53 | {{ end }} 54 | -------------------------------------------------------------------------------- /chart/trader/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: extensions/v1beta1 16 | kind: Deployment 17 | metadata: 18 | name: "{{ .Chart.Name }}-deployment" 19 | labels: 20 | chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' 21 | spec: 22 | replicas: {{ .Values.replicaCount }} 23 | revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} 24 | template: 25 | metadata: 26 | labels: 27 | app: "{{ .Chart.Name }}-selector" 28 | version: "current" 29 | spec: 30 | containers: 31 | - name: "{{ .Chart.Name }}" 32 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 33 | imagePullPolicy: {{ .Values.image.pullPolicy }} 34 | {{ if .Values.istio.enabled }} 35 | readinessProbe: 36 | httpGet: 37 | path: /trader/health 38 | port: 9080 39 | initialDelaySeconds: 20 40 | {{ end }} 41 | resources: 42 | requests: 43 | cpu: "{{ .Values.image.resources.requests.cpu }}" 44 | memory: "{{ .Values.image.resources.requests.memory }}" 45 | env: 46 | - name: PORT 47 | value: "{{ .Values.service.servicePort }}" 48 | - name: APPLICATION_NAME 49 | value: "{{ .Release.Name }}" 50 | - name: JWT_AUDIENCE 51 | valueFrom: 52 | secretKeyRef: 53 | name: jwt 54 | key: audience 55 | - name: JWT_ISSUER 56 | valueFrom: 57 | secretKeyRef: 58 | name: jwt 59 | key: issuer 60 | - name: OIDC_NAME 61 | valueFrom: 62 | secretKeyRef: 63 | name: oidc 64 | key: name 65 | optional: true 66 | - name: OIDC_ISSUER 67 | valueFrom: 68 | secretKeyRef: 69 | name: oidc 70 | key: issuer 71 | optional: true 72 | - name: OIDC_AUTH 73 | valueFrom: 74 | secretKeyRef: 75 | name: oidc 76 | key: auth 77 | optional: true 78 | - name: OIDC_TOKEN 79 | valueFrom: 80 | secretKeyRef: 81 | name: oidc 82 | key: token 83 | optional: true 84 | - name: OIDC_ID 85 | valueFrom: 86 | secretKeyRef: 87 | name: oidc 88 | key: id 89 | optional: true 90 | - name: OIDC_SECRET 91 | valueFrom: 92 | secretKeyRef: 93 | name: oidc 94 | key: secret 95 | optional: true 96 | - name: OIDC_KEY 97 | valueFrom: 98 | secretKeyRef: 99 | name: oidc 100 | key: key 101 | optional: true 102 | - name: OIDC_NODEPORT 103 | valueFrom: 104 | secretKeyRef: 105 | name: oidc 106 | key: nodeport 107 | optional: true 108 | {{- if .Values.generatedBindings.enabled }} 109 | {{.Files.Get "bindings.yaml" | indent 10 }} 110 | {{- end }} 111 | -------------------------------------------------------------------------------- /chart/trader/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | {{ if .Values.hpa.enabled }} 16 | {{ if and (eq .Capabilities.KubeVersion.Major "1") (ge .Capabilities.KubeVersion.Minor "8") }} 17 | apiVersion: autoscaling/v2beta1 18 | {{ else }} 19 | apiVersion: autoscaling/v2alpha1 20 | {{ end }} 21 | kind: HorizontalPodAutoscaler 22 | metadata: 23 | name: "{{ .Chart.Name }}-hpa-policy" 24 | namespace: 25 | spec: 26 | scaleTargetRef: 27 | apiVersion: apps/v1beta1 28 | kind: Deployment 29 | name: "{{ .Chart.Name }}-deployment" 30 | minReplicas: {{ .Values.hpa.minReplicas }} 31 | maxReplicas: {{ .Values.hpa.maxReplicas }} 32 | metrics: 33 | - type: Resource 34 | resource: 35 | name: cpu 36 | targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} 37 | - type: Resource 38 | resource: 39 | name: memory 40 | targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} 41 | {{ end }} 42 | -------------------------------------------------------------------------------- /chart/trader/templates/istio.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | {{ if .Values.istio.enabled }} 16 | apiVersion: config.istio.io/v1alpha2 17 | kind: RouteRule 18 | metadata: 19 | name: "{{ .Chart.Name }}-default" 20 | spec: 21 | destination: 22 | name: "{{ .Chart.Name }}-service" 23 | precedence: 1 24 | route: 25 | - labels: 26 | version: "current" 27 | weight: {{ .Values.istio.weight }} 28 | {{ if .Values.base.enabled }} 29 | - labels: 30 | version: "base" 31 | weight: {{ .Values.base.weight }} 32 | {{ end }} 33 | {{ end }} 34 | -------------------------------------------------------------------------------- /chart/trader/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: "{{ .Chart.Name }}-service" 5 | labels: 6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - name: http 11 | port: {{ .Values.service.servicePort }} 12 | - name: https 13 | port: {{ .Values.service.servicePortHttps }} 14 | selector: 15 | app: "{{ .Chart.Name }}-selector" -------------------------------------------------------------------------------- /chart/trader/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This is a YAML-formatted file. 16 | # Declare variables to be passed into your templates. 17 | replicaCount: 1 18 | revisionHistoryLimit: 1 19 | image: 20 | repository: trader 21 | tag: v1.0.0 22 | pullPolicy: IfNotPresent 23 | resources: 24 | requests: 25 | cpu: 200m 26 | memory: 300Mi 27 | service: 28 | name: Node 29 | type: NodePort 30 | servicePort: 9080 31 | servicePortHttps: 9443 32 | hpa: 33 | enabled: false 34 | minReplicas: 1 35 | maxReplicas: 2 36 | metrics: 37 | cpu: 38 | targetAverageUtilization: 70 39 | memory: 40 | targetAverageUtilization: 70 41 | base: 42 | enabled: false 43 | replicaCount: 1 44 | image: 45 | tag : v0.9.9 46 | weight: 100 47 | istio: 48 | enabled: false 49 | weight: 100 50 | generatedBindings: 51 | enabled: true 52 | -------------------------------------------------------------------------------- /cli-config.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # The IBM version of this configuration 16 | version : 0.0.3 17 | 18 | # The container name used for the run container 19 | container-name-run : "trader" 20 | 21 | # The container name used for the tools container 22 | container-name-tools : "bx-dev-trader-tools" 23 | 24 | # The project root in the run container to mount to host-path-run 25 | container-path-run : "/project" 26 | # The project root in the tools container that will be mounted to host-path-tools 27 | container-path-tools : "/project" 28 | 29 | # The name of the Dockerfile used to create the run container 30 | dockerfile-run : "Dockerfile" 31 | # The name of the Dockerfile used to create the tools container 32 | dockerfile-tools : "Dockerfile-tools" 33 | 34 | # The name of image to create from dockerfile-run 35 | image-name-run : "trader" 36 | # The name of image to create from dockerfile-tools 37 | image-name-tools : "bx-dev-java-maven-tools" 38 | 39 | # The project root on the host for the run container to mount to container-path-run 40 | host-path-run : "target" 41 | 42 | # The command to build the code and docker image for RUN 43 | build-cmd-run : "mvn install -Dmaven.repo.local=/project/.m2/repository -Pbx-dev-build" 44 | 45 | # The command to execute tests for the code in the tools container 46 | test-cmd : "mvn install -Dmaven.repo.local=/project/.m2/repository -Pbx-dev-build" 47 | 48 | # The command to build the code and docker image for DEBUG 49 | build-cmd-debug : "mvn install -Dmaven.repo.local=/project/.m2/repository -Pbx-dev-build" 50 | 51 | # The command to execute debug of the code in the tools container 52 | debug-cmd : "server debug" 53 | 54 | # The port mappings between the host and the container in the form [host:container] 55 | container-port-map : "9080:9080,9443:9443" 56 | 57 | # The port mappings between the host and the container for the debug port in the form [host:container] 58 | container-port-map-debug : "7777:7777" 59 | 60 | # The relative path to the helm chart used for Kubernetes deployment 61 | chart-path : "chart/trader" 62 | -------------------------------------------------------------------------------- /evidence-template/summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "date": "2021-03-15T13:36:04Z", 4 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 5 | "pipeline_id": "mypipeline_1", 6 | "pipeline_run_id": "mypipeline_run_1", 7 | "evidences": [ 8 | { 9 | "evidence_type_id": "com.ibm.code_vulnerability_scan", 10 | "evidence_type_version": "1.0.0", 11 | "date": "2021-03-15T13:31:38.996Z", 12 | "result": "success", 13 | "pipeline_id": "mypipeline_1", 14 | "pipeline_run_id": "mypipeline_run_1", 15 | "issues": [], 16 | "artifacts": [ 17 | { 18 | "url": "https://cloud.ibm.com", 19 | "hash": null 20 | } 21 | ], 22 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 23 | "log": [] 24 | }, 25 | { 26 | "evidence_type_id": "com.ibm.code_bom_check", 27 | "evidence_type_version": "1.0.0", 28 | "date": "2021-03-15T13:31:48.781Z", 29 | "result": "success", 30 | "pipeline_id": "mypipeline_1", 31 | "pipeline_run_id": "mypipeline_run_1", 32 | "issues": [], 33 | "artifacts": [], 34 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 35 | "log": [] 36 | }, 37 | { 38 | "evidence_type_id": "com.ibm.code_cis_check", 39 | "evidence_type_version": "1.0.0", 40 | "date": "2021-03-15T13:31:45.556Z", 41 | "result": "success", 42 | "pipeline_id": "mypipeline_1", 43 | "pipeline_run_id": "mypipeline_run_1", 44 | "issues": [], 45 | "artifacts": [], 46 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 47 | "log": [] 48 | }, 49 | { 50 | "evidence_type_id": "com.ibm.unit_tests", 51 | "evidence_type_version": "1.0.0", 52 | "date": "2021-03-15T13:31:32.191Z", 53 | "result": "success", 54 | "pipeline_id": "mypipeline_1", 55 | "pipeline_run_id": "mypipeline_run_1", 56 | "issues": [], 57 | "artifacts": [], 58 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 59 | "log": [] 60 | }, 61 | { 62 | "evidence_type_id": "com.ibm.cloud.image_vulnerability_scan", 63 | "evidence_type_version": "1.0.0", 64 | "date": "2021-03-15T13:31:55.496Z", 65 | "result": "success", 66 | "pipeline_id": "mypipeline_1", 67 | "pipeline_run_id": "mypipeline_run_1", 68 | "issues": [], 69 | "artifacts": [], 70 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 71 | "log": [] 72 | }, 73 | { 74 | "evidence_type_id": "com.ibm.acceptance_tests", 75 | "evidence_type_version": "1.0.0", 76 | "date": "2021-03-15T13:35:06.530Z", 77 | "result": "success", 78 | "pipeline_id": "mypipeline_1", 79 | "pipeline_run_id": "mypipeline_run_1", 80 | "issues": [], 81 | "artifacts": [], 82 | "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/b71ac2564ef0b98f1032d189795994dc:286e3b40-5099-4d87-9f12-57d2e425c64b::", 83 | "log": [] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /lab/Lab Intro - Developing and Deploying Microservices into ICP - FINAL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/lab/Lab Intro - Developing and Deploying Microservices into ICP - FINAL.pdf -------------------------------------------------------------------------------- /lab/Stock Trader Sample - Diagram.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/lab/Stock Trader Sample - Diagram.pptx -------------------------------------------------------------------------------- /lab/Think Lab 7620 - Developing and Deploying Microservices into ICP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/lab/Think Lab 7620 - Developing and Deploying Microservices into ICP.pdf -------------------------------------------------------------------------------- /lab/stock-trader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/lab/stock-trader.png -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | applications: 17 | - instances: 1 18 | timeout: 180 19 | name: trader 20 | buildpack: liberty-for-java 21 | path: ./target/trader-1.0-SNAPSHOT.zip 22 | memory: 512M 23 | random-route: true 24 | -------------------------------------------------------------------------------- /manifests/demodeploy.yaml: -------------------------------------------------------------------------------- 1 | #Deploy the pod 2 | apiVersion: extensions/v1beta1 3 | kind: Deployment 4 | metadata: 5 | name: trader 6 | labels: 7 | solution: stock-trader 8 | annotations: { 9 | prism.subkind: Liberty 10 | } 11 | spec: 12 | replicas: 1 13 | template: 14 | metadata: 15 | labels: 16 | app: trader 17 | version: v1 18 | spec: 19 | containers: 20 | - name: trader 21 | # Note: this manifests/deploy.yaml will be used when we want to deploy this directly using kubectl or a helm chart. 22 | # Microclimate uses the Charts folder so we can actually have both options 23 | # image: ibmstocktrader/trader:basicregistry # Docker Hub with basic registry sign in 24 | image: bigloon.icp:8500/stock-trader/trader:demo # IBM Cloud Private 25 | #image: registry.ng.bluemix.net/stock_trader/trader:latest # IBM Container Service 26 | env: 27 | - name: JWT_AUDIENCE 28 | valueFrom: 29 | secretKeyRef: 30 | name: jwt 31 | key: audience 32 | - name: JWT_ISSUER 33 | valueFrom: 34 | secretKeyRef: 35 | name: jwt 36 | key: issuer 37 | - name: OIDC_NAME 38 | valueFrom: 39 | secretKeyRef: 40 | name: oidc 41 | key: name 42 | optional: true 43 | - name: OIDC_ISSUER 44 | valueFrom: 45 | secretKeyRef: 46 | name: oidc 47 | key: issuer 48 | optional: true 49 | - name: OIDC_AUTH 50 | valueFrom: 51 | secretKeyRef: 52 | name: oidc 53 | key: auth 54 | optional: true 55 | - name: OIDC_TOKEN 56 | valueFrom: 57 | secretKeyRef: 58 | name: oidc 59 | key: token 60 | optional: true 61 | - name: OIDC_ID 62 | valueFrom: 63 | secretKeyRef: 64 | name: oidc 65 | key: id 66 | optional: true 67 | - name: OIDC_SECRET 68 | valueFrom: 69 | secretKeyRef: 70 | name: oidc 71 | key: secret 72 | optional: true 73 | - name: OIDC_KEY 74 | valueFrom: 75 | secretKeyRef: 76 | name: oidc 77 | key: key 78 | optional: true 79 | - name: OIDC_NODEPORT 80 | valueFrom: 81 | secretKeyRef: 82 | name: oidc 83 | key: nodeport 84 | optional: true 85 | ports: 86 | - containerPort: 9080 87 | - containerPort: 9443 88 | imagePullPolicy: Always 89 | readinessProbe: 90 | httpGet: 91 | path: /health 92 | port: 9080 93 | initialDelaySeconds: 30 94 | periodSeconds: 5 95 | livenessProbe: 96 | httpGet: 97 | path: /health 98 | port: 9080 99 | initialDelaySeconds: 60 100 | periodSeconds: 5 101 | resources: 102 | limits: 103 | cpu: 500m 104 | memory: 500Mi 105 | requests: 106 | cpu: 100m 107 | memory: 128Mi 108 | --- 109 | #Deploy the autoscaler 110 | apiVersion: autoscaling/v1 111 | kind: HorizontalPodAutoscaler 112 | metadata: 113 | name: trader 114 | labels: 115 | solution: stock-trader 116 | spec: 117 | scaleTargetRef: 118 | apiVersion: apps/v1 119 | kind: Deployment 120 | name: trader 121 | maxReplicas: 10 122 | minReplicas: 2 123 | targetCPUUtilizationPercentage: 50 124 | --- 125 | #Deploy the service 126 | apiVersion: v1 127 | kind: Service 128 | metadata: 129 | name: trader-service 130 | labels: 131 | app: trader 132 | solution: stock-trader 133 | spec: 134 | type: NodePort 135 | ports: 136 | - name: http 137 | protocol: TCP 138 | port: 9080 139 | targetPort: 9080 140 | - name: https 141 | protocol: TCP 142 | port: 9443 143 | targetPort: 9443 144 | selector: 145 | app: trader 146 | --- 147 | #Configure the ingress 148 | apiVersion: extensions/v1beta1 149 | kind: Ingress 150 | metadata: 151 | name: trader-ingress 152 | labels: 153 | solution: stock-trader 154 | annotations: 155 | #kubernetes.io/ingress.class: "istio" 156 | #ingress.kubernetes.io/rewrite-target: /trader 157 | kubernetes.io/ingress.class: "nginx" 158 | ingress.kubernetes.io/affinity: "cookie" 159 | ingress.kubernetes.io/session-cookie-name: "route" 160 | ingress.kubernetes.io/session-cookie-hash: "sha1" 161 | #ingress.kubernetes.io/rewrite-target: / 162 | ingress.kubernetes.io/secure-backends: "true" 163 | #ingress.kubernetes.io/app-root: "/trader" 164 | spec: 165 | rules: 166 | - host: 167 | http: 168 | paths: 169 | - path: /trader 170 | backend: 171 | serviceName: trader-service 172 | servicePort: 9443 173 | -------------------------------------------------------------------------------- /manifests/deploy-openshift.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #Deploy the pod 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: trader 20 | spec: 21 | replicas: 1 22 | selector: 23 | matchLabels: 24 | app: trader 25 | template: 26 | metadata: 27 | labels: 28 | app: trader 29 | annotations: 30 | prometheus.io/scrape: "true" 31 | prometheus.io/port: "9080" 32 | spec: 33 | containers: 34 | - name: trader 35 | image: quay-quay-quay.mycluster-us-sout-363772-a01ee4194ed985a1e32b1d96fd4ae346-0000.us-south.containers.appdomain.cloud/cicd/trader:latest # OCP 36 | envFrom: 37 | - configMapRef: 38 | name: s3-configmap 39 | env: 40 | - name: WLP_LOGGING_CONSOLE_FORMAT 41 | value: json 42 | - name: WLP_LOGGING_CONSOLE_SOURCE 43 | value: message,ffdc 44 | - name: JWT_AUDIENCE 45 | value: trader 46 | - name: JWT_ISSUER 47 | value: trader 48 | - name: OIDC_CLIENTID 49 | valueFrom: 50 | secretKeyRef: 51 | name: keycloak-oidc 52 | key: OIDC_CLIENTID 53 | optional: true 54 | - name: OIDC_CLIENTSECRET 55 | valueFrom: 56 | secretKeyRef: 57 | name: keycloak-oidc 58 | key: OIDC_CLIENTSECRET 59 | optional: true 60 | - name: OIDC_DISCOVERY_URL 61 | valueFrom: 62 | secretKeyRef: 63 | name: keycloak-oidc 64 | key: OIDC_DISCOVERY_URL 65 | optional: true 66 | - name: PORTFOLIO_URL 67 | value: http://portfolio-service:9080/portfolio 68 | - name: AUTH_TYPE 69 | value: keycloak 70 | ports: 71 | - containerPort: 9080 72 | - containerPort: 9443 73 | imagePullPolicy: Always 74 | readinessProbe: 75 | httpGet: 76 | path: /health/ready 77 | port: 9080 78 | initialDelaySeconds: 30 79 | periodSeconds: 15 80 | successThreshold: 1 81 | failureThreshold: 3 82 | livenessProbe: 83 | httpGet: 84 | path: /health/live 85 | port: 9080 86 | initialDelaySeconds: 60 87 | periodSeconds: 15 88 | successThreshold: 1 89 | failureThreshold: 5 # Allow a little time to exec into the pod to grab FFDCs before it's killed 90 | resources: 91 | limits: 92 | cpu: 500m 93 | memory: 500Mi 94 | requests: 95 | cpu: 100m 96 | memory: 128Mi 97 | --- 98 | #Deploy the service 99 | apiVersion: v1 100 | kind: Service 101 | metadata: 102 | name: trader-service 103 | spec: 104 | ports: 105 | - name: http 106 | protocol: TCP 107 | port: 9080 108 | targetPort: 9080 109 | - name: https 110 | protocol: TCP 111 | port: 9443 112 | targetPort: 9443 113 | selector: 114 | app: trader 115 | --- 116 | # Openshift Route 117 | kind: Route 118 | apiVersion: route.openshift.io/v1 119 | metadata: 120 | name: trader 121 | spec: 122 | to: 123 | kind: Service 124 | name: trader-service 125 | weight: 100 126 | port: 127 | targetPort: http 128 | tls: 129 | termination: edge 130 | -------------------------------------------------------------------------------- /manifests/deploy.yaml: -------------------------------------------------------------------------------- 1 | #Deploy the pod 2 | apiVersion: extensions/v1beta1 3 | kind: Deployment 4 | metadata: 5 | name: trader 6 | labels: 7 | app: stock-trader 8 | annotations: 9 | prism.subkind: Liberty 10 | spec: 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | app: trader 16 | annotations: 17 | prometheus.io/scrape: "true" 18 | prometheus.io/port: "9080" 19 | spec: 20 | containers: 21 | - name: trader 22 | # Note: this manifests/deploy.yaml will be used when we want to deploy this directly using kubectl. 23 | # Microclimate uses the Charts folder so we can actually have both options 24 | image: ibmstocktrader/trader:basicregistry # Docker Hub with basic registry sign in 25 | #image: mycluster.icp:8500/stock-trader/trader:latest # IBM Cloud Private 26 | #image: registry.ng.bluemix.net/stock_trader/trader:latest # IBM Kubernetes Service 27 | env: 28 | - name: JWT_AUDIENCE 29 | valueFrom: 30 | secretKeyRef: 31 | name: jwt 32 | key: audience 33 | - name: JWT_ISSUER 34 | valueFrom: 35 | secretKeyRef: 36 | name: jwt 37 | key: issuer 38 | - name: WLP_LOGGING_CONSOLE_FORMAT 39 | value: json 40 | - name: WLP_LOGGING_CONSOLE_SOURCE 41 | value: message,ffdc 42 | - name: OIDC_NAME 43 | valueFrom: 44 | secretKeyRef: 45 | name: oidc 46 | key: name 47 | optional: true 48 | - name: OIDC_ISSUER 49 | valueFrom: 50 | secretKeyRef: 51 | name: oidc 52 | key: issuer 53 | optional: true 54 | - name: OIDC_AUTH 55 | valueFrom: 56 | secretKeyRef: 57 | name: oidc 58 | key: auth 59 | optional: true 60 | - name: OIDC_TOKEN 61 | valueFrom: 62 | secretKeyRef: 63 | name: oidc 64 | key: token 65 | optional: true 66 | - name: OIDC_ID 67 | valueFrom: 68 | secretKeyRef: 69 | name: oidc 70 | key: id 71 | optional: true 72 | - name: OIDC_SECRET 73 | valueFrom: 74 | secretKeyRef: 75 | name: oidc 76 | key: secret 77 | optional: true 78 | - name: OIDC_KEY 79 | valueFrom: 80 | secretKeyRef: 81 | name: oidc 82 | key: key 83 | optional: true 84 | - name: OIDC_NODEPORT 85 | valueFrom: 86 | secretKeyRef: 87 | name: oidc 88 | key: nodeport 89 | optional: true 90 | - name: PORTFOLIO_URL 91 | valueFrom: 92 | configMapKeyRef: 93 | name: urls 94 | key: portfolio 95 | optional: true 96 | ports: 97 | - containerPort: 9080 98 | - containerPort: 9443 99 | imagePullPolicy: Always 100 | readinessProbe: 101 | httpGet: 102 | path: /health/ready 103 | port: 9080 104 | initialDelaySeconds: 30 105 | periodSeconds: 15 106 | successThreshold: 1 107 | failureThreshold: 3 108 | livenessProbe: 109 | httpGet: 110 | path: /health/live 111 | port: 9080 112 | initialDelaySeconds: 60 113 | periodSeconds: 15 114 | successThreshold: 1 115 | failureThreshold: 5 # Allow a little time to exec into the pod to grab FFDCs before it's killed 116 | resources: 117 | limits: 118 | cpu: 500m 119 | memory: 500Mi 120 | requests: 121 | cpu: 100m 122 | memory: 128Mi 123 | --- 124 | #Deploy the autoscaler 125 | apiVersion: autoscaling/v1 126 | kind: HorizontalPodAutoscaler 127 | metadata: 128 | name: trader-hpa 129 | labels: 130 | solution: stock-trader 131 | spec: 132 | scaleTargetRef: 133 | apiVersion: apps/v1 134 | kind: Deployment 135 | name: trader 136 | maxReplicas: 10 137 | minReplicas: 2 138 | targetCPUUtilizationPercentage: 60 139 | --- 140 | #Deploy the service 141 | apiVersion: v1 142 | kind: Service 143 | metadata: 144 | name: trader-service 145 | labels: 146 | app: trader 147 | solution: stock-trader 148 | annotations: 149 | prometheus.io/scrape: "true" 150 | prometheus.io/port: "9080" 151 | spec: 152 | type: NodePort 153 | ports: 154 | - name: http 155 | protocol: TCP 156 | port: 9080 157 | targetPort: 9080 158 | - name: https 159 | protocol: TCP 160 | port: 9443 161 | targetPort: 9443 162 | selector: 163 | app: trader 164 | --- 165 | #Configure the ingress 166 | apiVersion: extensions/v1beta1 167 | kind: Ingress 168 | metadata: 169 | name: trader-ingress 170 | labels: 171 | solution: stock-trader 172 | annotations: 173 | #kubernetes.io/ingress.class: "istio" 174 | #ingress.kubernetes.io/rewrite-target: /trader 175 | kubernetes.io/ingress.class: "nginx" 176 | ingress.kubernetes.io/affinity: "cookie" 177 | ingress.kubernetes.io/session-cookie-name: "route" 178 | ingress.kubernetes.io/session-cookie-hash: "sha1" 179 | #ingress.kubernetes.io/rewrite-target: / 180 | ingress.kubernetes.io/secure-backends: "true" 181 | #ingress.kubernetes.io/app-root: "/trader" 182 | spec: 183 | rules: 184 | - host: 185 | http: 186 | paths: 187 | - path: /trader 188 | backend: 189 | serviceName: trader-service 190 | servicePort: 9443 191 | -------------------------------------------------------------------------------- /manifests/trader-values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | ######################################################################################## 17 | ## Configuration for deploying Trader microservice using WebSphere Liberty helm chart 18 | ######################################################################################## 19 | 20 | image: 21 | # Docker Hub with basic registry sign in 22 | repository: ibmstocktrader/trader 23 | tag: basicregistry 24 | # IBM Cloud Private 25 | #repository: mycluster.icp:8500/stock-trader/trader 26 | #tag: latest 27 | # IBM Container Service 28 | #repository: registry.ng.bluemix.net/stock_trader/trader 29 | #tag: latest 30 | pullPolicy: Always 31 | extraEnvs: 32 | - name: JWT_AUDIENCE 33 | valueFrom: 34 | secretKeyRef: 35 | name: jwt 36 | key: audience 37 | - name: JWT_ISSUER 38 | valueFrom: 39 | secretKeyRef: 40 | name: jwt 41 | key: issuer 42 | - name: OIDC_NAME 43 | valueFrom: 44 | secretKeyRef: 45 | name: oidc 46 | key: name 47 | optional: true 48 | - name: OIDC_ISSUER 49 | valueFrom: 50 | secretKeyRef: 51 | name: oidc 52 | key: issuer 53 | optional: true 54 | - name: OIDC_AUTH 55 | valueFrom: 56 | secretKeyRef: 57 | name: oidc 58 | key: auth 59 | optional: true 60 | - name: OIDC_TOKEN 61 | valueFrom: 62 | secretKeyRef: 63 | name: oidc 64 | key: token 65 | optional: true 66 | - name: OIDC_ID 67 | valueFrom: 68 | secretKeyRef: 69 | name: oidc 70 | key: id 71 | optional: true 72 | - name: OIDC_SECRET 73 | valueFrom: 74 | secretKeyRef: 75 | name: oidc 76 | key: secret 77 | optional: true 78 | - name: OIDC_KEY 79 | valueFrom: 80 | secretKeyRef: 81 | name: oidc 82 | key: key 83 | optional: true 84 | - name: OIDC_NODEPORT 85 | valueFrom: 86 | secretKeyRef: 87 | name: oidc 88 | key: nodeport 89 | optional: true 90 | 91 | resourceNameOverride: stock-trader 92 | 93 | pod: 94 | labels: 95 | solution: stock-trader 96 | version: v1 97 | 98 | service: 99 | enabled: true 100 | name: trader-service 101 | port: 9443 102 | targetPort: 9443 103 | type: NodePort 104 | extraPorts: 105 | - name: trader-service-http 106 | protocol: TCP 107 | port: 9080 108 | targetPort: 9080 109 | 110 | ingress: 111 | enabled: true 112 | path: "/trader" 113 | annotations: 114 | #kubernetes.io/ingress.class: "istio" 115 | kubernetes.io/ingress.class: "nginx" 116 | ingress.kubernetes.io/affinity: "cookie" 117 | nginx.ingress.kubernetes.io/affinity: "cookie" 118 | ingress.kubernetes.io/session-cookie-name: "route" 119 | nginx.ingress.kubernetes.io/session-cookie-name: "route" 120 | ingress.kubernetes.io/session-cookie-hash: "sha1" 121 | nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" 122 | ingress.kubernetes.io/secure-backends: "true" 123 | nginx.ingress.kubernetes.io/secure-backends: "true" 124 | #ingress.kubernetes.io/app-root: "/trader" 125 | #nginx.ingress.kubernetes.io/app-root: "/trader" 126 | #ingress.kubernetes.io/rewrite-target: /trader 127 | #nginx.ingress.kubernetes.io/rewrite-target: /trader 128 | 129 | monitoring: 130 | enabled: true -------------------------------------------------------------------------------- /pipeline-template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 IBM Corp All Rights Reserved 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: v1 16 | kind: Template 17 | metadata: 18 | name: stocktrader-ocp-native-pipeline 19 | parameters: 20 | - name: APP_NAME 21 | description: The name assigned to all of the application objects defined in this template. 22 | displayName: Application Name 23 | required: true 24 | - name: GIT_SOURCE_URL 25 | description: The source URL for the application 26 | displayName: Source URL 27 | required: true 28 | - name: GIT_SOURCE_REF 29 | description: The source Ref for the application 30 | displayName: Source Ref 31 | required: true 32 | - name: EXTRACT_KEYCLOAK_CERT 33 | description: If set to true, extracts the Keycloak certificate and adds it to the Java keystore. Should only be used for testing purposes with self-signed certificates. 34 | displayName: Extract Keycloak certificate 35 | required: true 36 | value: "true" 37 | - name: KEYCLOAK_CONNECTION_STRING 38 | description: If the "Extract Keycloak certificate" is set to yes, this parameter determines from where the certificate is extracted. Has to be openssl cli tool conform string, e.g. "server.com:443". Do not add a protocol like http. 39 | displayName: Keycloak connection string 40 | required: false 41 | value: "keycloak.yourdomain.com:443" 42 | objects: 43 | - apiVersion: v1 44 | kind: ImageStream 45 | metadata: 46 | labels: 47 | app: ${APP_NAME} 48 | name: ${APP_NAME} 49 | spec: {} 50 | status: 51 | dockerImageRepository: "" 52 | - apiVersion: v1 53 | kind: BuildConfig 54 | metadata: 55 | labels: 56 | app: ${APP_NAME} 57 | name: ${APP_NAME} 58 | spec: 59 | output: 60 | to: 61 | kind: ImageStreamTag 62 | name: ${APP_NAME}:latest 63 | postCommit: {} 64 | resources: {} 65 | runPolicy: Serial 66 | source: 67 | git: 68 | uri: ${GIT_SOURCE_URL} 69 | ref: ${GIT_SOURCE_REF} 70 | type: Docker 71 | strategy: 72 | type: Docker 73 | dockerStrategy: 74 | buildArgs: 75 | - name: keycloak_connection_string 76 | value: ${KEYCLOAK_CONNECTION_STRING} 77 | - name: extract_keycloak_cert 78 | value: ${EXTRACT_KEYCLOAK_CERT} 79 | triggers: 80 | - type: "GitHub" 81 | github: 82 | secretReference: 83 | name: "github-webhook" 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 4.0.0 19 | 20 | com.stocktrader 21 | trader 22 | 1.0-SNAPSHOT 23 | war 24 | 25 | StockTrader - trader 26 | 27 | 28 | 34 | 35 | UTF-8 36 | UTF-8 37 | 17 38 | 39 | trader 40 | ${app.name} 41 | 42 | 43 | 9080 44 | 9443 45 | ${project.artifactId} 46 | ${project.artifactId} 47 | true 48 | none 49 | stock 50 | trader 51 | 52 | 53 | 54 | 55 | org.eclipse.microprofile 56 | microprofile 57 | 6.1 58 | pom 59 | provided 60 | 61 | 62 | jakarta.platform 63 | jakarta.jakartaee-web-api 64 | 10.0.0 65 | provided 66 | 67 | 68 | 69 | org.apache.commons 70 | commons-math3 71 | 3.6.1 72 | 73 | 74 | 75 | io.jaegertracing 76 | jaeger-client 77 | 1.8.1 78 | 79 | 80 | org.slf4j 81 | slf4j-jdk14 82 | 2.0.17 83 | 84 | 85 | com.ibm.cos 86 | ibm-cos-java-sdk-s3 87 | 2.14.1 88 | 89 | 90 | 91 | junit 92 | junit 93 | 4.13.2 94 | test 95 | 96 | 97 | org.apache.cxf 98 | cxf-rt-rs-client 99 | 4.1.1 100 | test 101 | 102 | 103 | jakarta.xml.bind 104 | jakarta.xml.bind-api 105 | 4.0.2 106 | test 107 | 108 | 109 | 110 | 111 | TraderUI 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-war-plugin 116 | 3.4.0 117 | 118 | false 119 | pom.xml 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-compiler-plugin 125 | 3.14.0 126 | 127 | 17 128 | 17 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-resources-plugin 134 | 3.3.1 135 | 136 | 137 | 138 | io.openliberty.tools 139 | liberty-maven-plugin 140 | 3.11.3 141 | 142 | 143 | start-server 144 | pre-integration-test 145 | 146 | test-start 147 | deploy 148 | 149 | 150 | false 151 | 152 | 153 | 154 | install-feature 155 | prepare-package 156 | 157 | create 158 | install-feature 159 | 160 | 161 | 162 | microprofile-6.1 163 | pages-3.1 164 | 165 | 166 | 167 | 168 | stop-server 169 | post-integration-test 170 | 171 | test-stop 172 | 173 | 174 | 175 | 176 | 177 | com.ibm.websphere.appserver.runtime 178 | wlp-webProfile10 179 | 25.0.0.3 180 | zip 181 | 182 | ${basedir}/src/main/liberty/config 183 | ${packaging.type} 184 | 185 | ${project.artifactId}-${project.version}.war 186 | ${testServerHttpPort} 187 | ${testServerHttpsPort} 188 | 189 | 190 | true 191 | 192 | false 193 | true 194 | 195 | 196 | 197 | org.apache.maven.plugins 198 | maven-failsafe-plugin 199 | 3.5.2 200 | 201 | 202 | 203 | integration-test 204 | verify 205 | 206 | 207 | 208 | 209 | 210 | ${liberty.var.default.http.port} 211 | ${liberty.var.default.https.port} 212 | ${liberty.var.app.context.root} 213 | stock 214 | trader 215 | none 216 | true 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/AddPortfolio.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2025 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 21 | 22 | import java.io.IOException; 23 | import java.util.logging.Logger; 24 | 25 | //CDI 1.2 26 | import jakarta.inject.Inject; 27 | import jakarta.enterprise.context.ApplicationScoped; 28 | 29 | //Servlet 4.0 30 | import jakarta.servlet.ServletException; 31 | import jakarta.servlet.annotation.HttpConstraint; 32 | import jakarta.servlet.annotation.ServletSecurity; 33 | import jakarta.servlet.annotation.WebServlet; 34 | import jakarta.servlet.http.HttpServlet; 35 | import jakarta.servlet.http.HttpServletRequest; 36 | import jakarta.servlet.http.HttpServletResponse; 37 | import jakarta.servlet.RequestDispatcher; 38 | 39 | //mpJWT 1.0 40 | import org.eclipse.microprofile.jwt.JsonWebToken; 41 | 42 | //mpRestClient 1.0 43 | import org.eclipse.microprofile.rest.client.inject.RestClient; 44 | 45 | /** 46 | * Servlet implementation class AddPortfolio 47 | */ 48 | @WebServlet(description = "Add Portfolio servlet", urlPatterns = { "/addPortfolio" }) 49 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader" } )) 50 | @ApplicationScoped 51 | public class AddPortfolio extends HttpServlet { 52 | private static final long serialVersionUID = 4815162342L; 53 | private static Logger logger = Logger.getLogger(AddPortfolio.class.getName()); 54 | private static Utilities utilities = null; 55 | 56 | private @Inject @RestClient BrokerClient brokerClient; 57 | 58 | private @Inject JsonWebToken jwt; 59 | 60 | /** 61 | * @see HttpServlet#HttpServlet() 62 | */ 63 | public AddPortfolio() { 64 | super(); 65 | 66 | if (utilities == null) utilities = new Utilities(logger); 67 | } 68 | 69 | /** 70 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 71 | */ 72 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 73 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/addPortfolio.jsp"); 74 | dispatcher.forward(request, response); 75 | } 76 | 77 | /** 78 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 79 | */ 80 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 81 | String owner = request.getParameter("owner"); 82 | double balance = Double.parseDouble(request.getParameter("balance")); 83 | String currency = request.getParameter("currency"); 84 | 85 | if ((owner!=null) && !owner.equals("")) try { 86 | logger.info("Redirecting to Summary servlet."); 87 | 88 | //PortfolioServices.createPortfolio(request, owner); 89 | brokerClient.createBroker(utilities.getAuthHeader(jwt, request), owner, balance, currency); 90 | 91 | response.sendRedirect("summary"); //send control to the Summary servlet 92 | } catch (Throwable t) { 93 | logger.warning("Error creating portfolio: "+t.getMessage()); 94 | 95 | String message = "Error creating portfolio. Please check the trader, broker and portfolio pod logs for details."; 96 | 97 | //send control to the Display Message servlet 98 | response.sendRedirect("message?message="+message); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/AddStock.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2025 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Broker; 22 | 23 | import java.io.IOException; 24 | import java.math.RoundingMode; 25 | import java.text.NumberFormat; 26 | import java.util.logging.Logger; 27 | 28 | //CDI 1.2 29 | import jakarta.inject.Inject; 30 | import jakarta.enterprise.context.ApplicationScoped; 31 | 32 | //Servlet 3.1 33 | import jakarta.servlet.ServletException; 34 | import jakarta.servlet.annotation.HttpConstraint; 35 | import jakarta.servlet.annotation.ServletSecurity; 36 | import jakarta.servlet.annotation.WebServlet; 37 | import jakarta.servlet.http.HttpServlet; 38 | import jakarta.servlet.http.HttpServletRequest; 39 | import jakarta.servlet.http.HttpServletResponse; 40 | import jakarta.servlet.RequestDispatcher; 41 | 42 | //mpJWT 1.0 43 | import org.eclipse.microprofile.jwt.JsonWebToken; 44 | 45 | //mpRestClient 1.0 46 | import org.eclipse.microprofile.rest.client.inject.RestClient; 47 | 48 | /** 49 | * Servlet implementation class AddStock 50 | */ 51 | @WebServlet(description = "Add Stock servlet", urlPatterns = { "/addStock" }) 52 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader" } )) 53 | @ApplicationScoped 54 | public class AddStock extends HttpServlet { 55 | private static final long serialVersionUID = 4815162342L; 56 | private static final String SELL = "Sell"; 57 | private static final String SUBMIT = "Submit"; 58 | 59 | private static Logger logger = Logger.getLogger(AddStock.class.getName()); 60 | 61 | private static Utilities utilities = null; 62 | private static NumberFormat currency = null; 63 | 64 | private @Inject @RestClient BrokerClient brokerClient; 65 | private @Inject JsonWebToken jwt; 66 | 67 | /** 68 | * @see HttpServlet#HttpServlet() 69 | */ 70 | public AddStock() { 71 | super(); 72 | 73 | if (utilities == null) { 74 | currency = NumberFormat.getNumberInstance(); 75 | currency.setMinimumFractionDigits(2); 76 | currency.setMaximumFractionDigits(2); 77 | currency.setRoundingMode(RoundingMode.HALF_UP); 78 | 79 | utilities = new Utilities(logger); 80 | } 81 | } 82 | 83 | /** 84 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 85 | */ 86 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 87 | String owner = request.getParameter("owner"); 88 | 89 | String commission = getCommission(request, owner); 90 | 91 | request.setAttribute("commission", commission); 92 | 93 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/addStock.jsp"); 94 | dispatcher.forward(request, response); 95 | } 96 | 97 | /** 98 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 99 | */ 100 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 101 | String owner = request.getParameter("owner"); 102 | String symbol = request.getParameter("symbol"); 103 | String shareString = request.getParameter("shares"); 104 | String action = request.getParameter("action"); 105 | String source = request.getParameter("source"); 106 | if (source == null) source = "summary"; 107 | 108 | String submit = request.getParameter("submit"); 109 | if ((submit!=null) && submit.equals(SUBMIT)) { //don't do if they chose Cancel 110 | if ((shareString!=null) && !shareString.equals("")) { 111 | int shares = Integer.parseInt(shareString); 112 | if (action.equalsIgnoreCase(SELL)) shares *= -1; //selling means buying a negative number of shares 113 | 114 | //PortfolioServices.updatePortfolio(request, owner, symbol, shares); 115 | brokerClient.updateBroker(utilities.getAuthHeader(jwt, request), owner, symbol, shares); 116 | } 117 | } 118 | 119 | if (source.equalsIgnoreCase("viewPortfolio")) source += "?owner="+owner; 120 | response.sendRedirect(source); 121 | } 122 | 123 | private String getCommission(HttpServletRequest request, String owner) { 124 | String formattedCommission = "Free!"; 125 | try { 126 | logger.info("Getting commission"); 127 | //JsonObject portfolio = PortfolioServices.getPortfolio(request, owner); 128 | Broker broker = brokerClient.getBroker(utilities.getAuthHeader(jwt, request), owner); 129 | double commission = broker.getNextCommission(); 130 | if (commission!=0.0) formattedCommission = "$"+currency.format(commission); 131 | logger.info("Got commission: "+formattedCommission); 132 | } catch (Throwable t) { 133 | utilities.logException(t); 134 | } 135 | return formattedCommission; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/DisplayMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import java.io.IOException; 21 | import java.io.Writer; 22 | import java.net.URLDecoder; 23 | import java.util.logging.Logger; 24 | 25 | //Servlet 3.1 26 | import jakarta.servlet.ServletException; 27 | import jakarta.servlet.annotation.HttpConstraint; 28 | import jakarta.servlet.annotation.ServletSecurity; 29 | import jakarta.servlet.annotation.WebServlet; 30 | import jakarta.servlet.http.HttpServlet; 31 | import jakarta.servlet.http.HttpServletRequest; 32 | import jakarta.servlet.http.HttpServletResponse; 33 | import jakarta.servlet.RequestDispatcher; 34 | 35 | /** 36 | * Servlet implementation class DisplayMessage 37 | */ 38 | @WebServlet(description = "Display Message servlet", urlPatterns = { "/message" }) 39 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader" } )) 40 | public class DisplayMessage extends HttpServlet { 41 | private static final long serialVersionUID = 4815162342L; 42 | private static Logger logger = Logger.getLogger(DisplayMessage.class.getName()); 43 | 44 | /** 45 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 46 | */ 47 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 48 | String encodedMessage = request.getParameter("message"); 49 | if(encodedMessage == null) encodedMessage = ""; 50 | String message = URLDecoder.decode(encodedMessage, "UTF-8"); 51 | request.setAttribute("message", message); 52 | 53 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/message.jsp"); 54 | dispatcher.forward(request, response); 55 | } 56 | 57 | /** 58 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 59 | */ 60 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 61 | String owner = request.getParameter("owner"); 62 | 63 | if ((owner==null) || owner.equals("")) { 64 | logger.info("Redirecting to summary servlet"); 65 | response.sendRedirect("summary"); //send control to the Summary servlet 66 | } else { 67 | logger.info("Redirecting to viewPortfolio servlet"); 68 | response.sendRedirect("viewPortfolio?owner="+owner); //send control to the ViewPortfolio servlet 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/Error.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import java.io.IOException; 21 | import java.io.Writer; 22 | 23 | //Servlet 3.1 24 | import jakarta.servlet.ServletException; 25 | import jakarta.servlet.annotation.WebServlet; 26 | import jakarta.servlet.http.HttpServlet; 27 | import jakarta.servlet.http.HttpServletRequest; 28 | import jakarta.servlet.http.HttpServletResponse; 29 | import jakarta.servlet.RequestDispatcher; 30 | 31 | /** 32 | * Servlet implementation class Error 33 | */ 34 | @WebServlet(description = "Error servlet", urlPatterns = { "/error" }) 35 | public class Error extends HttpServlet { 36 | private static final long serialVersionUID = 4815162342L; 37 | /** 38 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 39 | */ 40 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 41 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/error.jsp"); 42 | dispatcher.forward(request, response); 43 | } 44 | 45 | /** 46 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 47 | */ 48 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 49 | response.sendRedirect("login"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/Login.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import java.io.IOException; 21 | import java.util.logging.Logger; 22 | 23 | 24 | //Servlet 4.0 25 | import jakarta.servlet.ServletException; 26 | import jakarta.servlet.annotation.WebServlet; 27 | import jakarta.servlet.http.Cookie; 28 | import jakarta.servlet.http.HttpServlet; 29 | import jakarta.servlet.http.HttpServletRequest; 30 | import jakarta.servlet.http.HttpServletResponse; 31 | import jakarta.servlet.RequestDispatcher; 32 | 33 | /** 34 | * Servlet implementation class Login 35 | */ 36 | @WebServlet(description = "Login servlet", urlPatterns = { "/login" }) 37 | public class Login extends HttpServlet { 38 | private static final long serialVersionUID = 4815162342L; 39 | private static Logger logger = Logger.getLogger(Login.class.getName()); 40 | private static Utilities utilities = null; 41 | 42 | /** 43 | * @see HttpServlet#HttpServlet() 44 | */ 45 | public Login() { 46 | super(); 47 | 48 | if (utilities == null) utilities = new Utilities(logger); 49 | } 50 | 51 | /** 52 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 53 | */ 54 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 55 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/login.jsp"); 56 | dispatcher.forward(request, response); 57 | } 58 | 59 | /** 60 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 61 | */ 62 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 63 | boolean success = false; 64 | String id = request.getParameter("id"); 65 | String password = request.getParameter("password"); 66 | 67 | try { 68 | if (request.getUserPrincipal() != null) request.logout(); //in case there's a left over auth cookie but we ended up here 69 | 70 | request.login(id, password); 71 | if (utilities.useBasicAuth) utilities.addBasicAuthCredentials(id, password); 72 | 73 | Cookie cookie = new Cookie("user", id); //clear text user id that can be used in Istio routing rules 74 | response.addCookie(cookie); 75 | 76 | success = true; 77 | logger.info("Successfully logged in user: "+id); 78 | } catch (Throwable t) { 79 | utilities.logException(t); 80 | } 81 | 82 | String url = "error"; 83 | if (success) url = "summary"; 84 | 85 | response.sendRedirect(url); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/SubmitFeedback.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2025 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Feedback; 22 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Stock; 23 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.WatsonInput; 24 | 25 | import java.io.IOException; 26 | import java.util.logging.Logger; 27 | import java.net.URLEncoder; 28 | 29 | //CDI 1.2 30 | import jakarta.inject.Inject; 31 | import jakarta.enterprise.context.ApplicationScoped; 32 | 33 | //JSON-P 1.0 (JSR 353). The replaces my old usage of IBM's JSON4J (com.ibm.json.java.JSONObject) 34 | import jakarta.json.Json; 35 | import jakarta.json.JsonObject; 36 | import jakarta.json.JsonObjectBuilder; 37 | 38 | //Servlet 4.0 39 | import jakarta.servlet.ServletException; 40 | import jakarta.servlet.annotation.HttpConstraint; 41 | import jakarta.servlet.annotation.ServletSecurity; 42 | import jakarta.servlet.annotation.WebServlet; 43 | import jakarta.servlet.http.HttpServlet; 44 | import jakarta.servlet.http.HttpServletRequest; 45 | import jakarta.servlet.http.HttpServletResponse; 46 | import jakarta.servlet.RequestDispatcher; 47 | 48 | //mpJWT 1.0 49 | import org.eclipse.microprofile.jwt.JsonWebToken; 50 | 51 | //mpRestClient 1.0 52 | import org.eclipse.microprofile.rest.client.inject.RestClient; 53 | 54 | /** 55 | * Servlet implementation class SubmitFeedback 56 | */ 57 | @WebServlet(description = "Submit Feedback servlet", urlPatterns = { "/feedback" }) 58 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader" } )) 59 | @ApplicationScoped 60 | public class SubmitFeedback extends HttpServlet { 61 | private static final long serialVersionUID = 4815162342L; 62 | private static final String SUBMIT = "Submit"; 63 | private static Logger logger = Logger.getLogger(SubmitFeedback.class.getName()); 64 | private static Utilities utilities = null; 65 | 66 | private @Inject @RestClient BrokerClient brokerClient; 67 | 68 | private @Inject JsonWebToken jwt; 69 | 70 | /** 71 | * @see HttpServlet#HttpServlet() 72 | */ 73 | public SubmitFeedback() { 74 | super(); 75 | 76 | if (utilities == null) utilities = new Utilities(logger); 77 | } 78 | 79 | /** 80 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 81 | */ 82 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 83 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/submitFeedback.jsp"); 84 | dispatcher.forward(request, response); 85 | } 86 | 87 | /** 88 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 89 | */ 90 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 91 | String owner = request.getParameter("owner"); 92 | String submit = request.getParameter("submit"); 93 | 94 | if (submit != null) { 95 | if (submit.equals(SUBMIT)) { 96 | String feedback = request.getParameter("feedback"); 97 | if ((feedback!=null) && !feedback.equals("") && (owner!=null) && !owner.equals("")) { 98 | WatsonInput input = new WatsonInput(feedback); 99 | 100 | logger.info("Calling broker/"+owner+"/feedback with following JSON: "+input.toString()); 101 | //JsonObject result = PortfolioServices.submitFeedback(request, owner, text); 102 | Feedback result = brokerClient.submitFeedback(utilities.getAuthHeader(jwt, request), owner, input); 103 | 104 | logger.info("broker/"+owner+"/feedback returned the following JSON: "+result!=null ? result.toString() : "null"); 105 | String message = result!=null ? result.getMessage() : "null"; 106 | String encodedMessage = URLEncoder.encode(message, "UTF-8"); 107 | response.sendRedirect("message?owner="+owner+"&message="+encodedMessage); //send control to the DisplayMessage servlet 108 | } else { 109 | logger.info("No feedback submitted"); 110 | response.sendRedirect("viewPortfolio?owner="+owner); //send control to the ViewPortfolio servlet 111 | } 112 | } else { //they hit Cancel instead 113 | logger.info("Feedback submission canceled"); 114 | response.sendRedirect("viewPortfolio?owner="+owner); //send control to the ViewPortfolio servlet 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/Summary.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Broker; 22 | 23 | import java.io.IOException; 24 | 25 | //JSR 47 Logging 26 | import java.util.ArrayList; 27 | import java.util.Base64; 28 | import java.util.Collections; 29 | import java.util.List; 30 | import java.util.concurrent.atomic.AtomicBoolean; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | import java.util.logging.Level; 33 | import java.util.logging.Logger; 34 | 35 | //CDI 2.0 36 | import jakarta.inject.Inject; 37 | import jakarta.enterprise.context.ApplicationScoped; 38 | 39 | //Servlet 4.0 40 | import jakarta.servlet.ServletException; 41 | import jakarta.servlet.annotation.HttpConstraint; 42 | import jakarta.servlet.annotation.ServletSecurity; 43 | import jakarta.servlet.annotation.WebServlet; 44 | import jakarta.servlet.http.HttpServlet; 45 | import jakarta.servlet.http.HttpServletRequest; 46 | import jakarta.servlet.http.HttpServletResponse; 47 | import jakarta.servlet.http.HttpSession; 48 | import jakarta.servlet.RequestDispatcher; 49 | 50 | //mpConfig 1.3 51 | import org.eclipse.microprofile.config.inject.ConfigProperty; 52 | 53 | //mpJWT 1.0 54 | import org.eclipse.microprofile.jwt.JsonWebToken; 55 | 56 | //mpRestClient 1.0 57 | import org.eclipse.microprofile.rest.client.inject.RestClient; 58 | 59 | import org.apache.commons.math3.stat.descriptive.SynchronizedDescriptiveStatistics; 60 | 61 | /** 62 | * Servlet implementation class Summary 63 | */ 64 | @WebServlet(description = "Broker summary servlet", urlPatterns = { "/summary" }) 65 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader", "StockViewer" } )) 66 | @ApplicationScoped 67 | public class Summary extends HttpServlet { 68 | private static final long serialVersionUID = 4815162342L; 69 | private static final String LOGOUT = "Log Out"; 70 | private static final String CREATE = "create"; 71 | private static final String RETRIEVE = "retrieve"; 72 | private static final String UPDATE = "update"; 73 | private static final String DELETE = "delete"; 74 | private static final String POST = "post"; 75 | private static final String TOKEN = "access_token"; 76 | private static final String JWT = "jwt"; 77 | private static Logger logger = Logger.getLogger(Summary.class.getName()); 78 | private static Utilities utilities = null; 79 | 80 | private @Inject @ConfigProperty(name = "TEST_MODE", defaultValue = "false") boolean testMode; 81 | private @Inject @RestClient BrokerClient brokerClient; 82 | private @Inject JsonWebToken jwt; 83 | 84 | //used in the liveness probe 85 | public static boolean error = false; 86 | public static String message = null; 87 | 88 | // New liveness probe by @rtclauss 89 | private static SynchronizedDescriptiveStatistics last1kCalls; 90 | public static AtomicBoolean IS_FAILED = new AtomicBoolean(false); 91 | private static final double FAILURE_THRESHOLD = 0.85; 92 | 93 | /** 94 | * @see HttpServlet#HttpServlet() 95 | */ 96 | public Summary() { 97 | super(); 98 | if(last1kCalls==null){ 99 | last1kCalls = new SynchronizedDescriptiveStatistics(1000); 100 | } 101 | if (utilities == null) utilities = new Utilities(logger); 102 | } 103 | 104 | /** 105 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 106 | */ 107 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 108 | if (brokerClient==null) { 109 | throw new NullPointerException("Injection of BrokerClient failed!"); 110 | } 111 | 112 | try { 113 | if (Utilities.useOIDC) { 114 | String method = request.getMethod(); 115 | //normally goGet gets called via a GET, but on redirect from a successful login against an OIDC provider (like KeyCloak), 116 | //we will get called via a POST, with a form param containing the JWT issued by the OIDC provider, which we'll stash in the session 117 | //TODO: Consider using a cookie instead of the session, since if there are multiple Trader pods, we'd need distributed session support enabled 118 | //(or some kind of "sticky session" support to get a given browser always routed to the same pod (though that isn't highly available, of course), 119 | //such as https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html) 120 | if (POST.equalsIgnoreCase(method)) { 121 | String token = request.getParameter(TOKEN); 122 | HttpSession session = request.getSession(); 123 | if (session!=null) { 124 | logger.info("Placing JWT in the http session"); 125 | session.setAttribute(JWT, token); 126 | if (logger.isLoggable(Level.FINE)) { 127 | logger.fine(TOKEN+" = "+token); 128 | Base64.Decoder decoder = Base64.getUrlDecoder(); 129 | String[] parts = token.split("\\."); 130 | String header = new String(decoder.decode(parts[0])); 131 | String payload = new String(decoder.decode(parts[1])); 132 | logger.fine("access token header = "+header); 133 | logger.fine("access token body = "+payload); 134 | } 135 | } 136 | } 137 | } else { 138 | if (jwt==null) throw new NullPointerException("Injection of JWT failed!"); 139 | } 140 | // JsonArray portfolios = PortfolioServices.getPortfolios(request); 141 | Broker[] brokers = testMode ? getHardcodedBrokers() : brokerClient.getBrokers(utilities.getAuthHeader(jwt, request)); 142 | 143 | // set brokers for JSP 144 | request.setAttribute("brokers", brokers); 145 | last1kCalls.addValue(1.0); 146 | } catch (Throwable t) { 147 | utilities.logException(t); 148 | message = t.getMessage(); 149 | error = true; 150 | request.setAttribute("message", message); 151 | request.setAttribute("error", error); 152 | last1kCalls.addValue(0.0); 153 | } finally { 154 | var mean = last1kCalls.getMean(); 155 | logger.finest("Is failing calc mean: "+ mean); 156 | if (mean < FAILURE_THRESHOLD) { 157 | logger.warning("Trader is failing liveness threshold"); 158 | IS_FAILED.set(true); 159 | } else { 160 | IS_FAILED.set(false); 161 | } 162 | } 163 | 164 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/summary.jsp"); 165 | dispatcher.forward(request, response); 166 | } 167 | 168 | /** 169 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 170 | */ 171 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 172 | String submit = request.getParameter("submit"); 173 | 174 | if (submit != null) { 175 | if (submit.equals(LOGOUT)) { 176 | request.logout(); 177 | 178 | HttpSession session = request.getSession(); 179 | if (session != null) session.invalidate(); 180 | 181 | response.sendRedirect("login"); 182 | } else { 183 | String action = request.getParameter("action"); 184 | String owner = request.getParameter("owner"); 185 | 186 | if (action != null) { 187 | if (action.equals(CREATE)) { 188 | response.sendRedirect("addPortfolio"); //send control to the AddPortfolio servlet 189 | } else if (action.equals(RETRIEVE)) { 190 | response.sendRedirect("viewPortfolio?owner="+owner); //send control to the ViewPortfolio servlet 191 | } else if (action.equals(UPDATE)) { 192 | response.sendRedirect("addStock?owner="+owner+"&source=summary"); //send control to the AddStock servlet 193 | } else if (action.equals(DELETE)) { 194 | // PortfolioServices.deletePortfolio(request, owner); 195 | brokerClient.deleteBroker(utilities.getAuthHeader(jwt, request), owner); 196 | doGet(request, response); //refresh the Summary servlet 197 | } else { 198 | doGet(request, response); //something went wrong - just refresh the Summary servlet 199 | } 200 | } else { 201 | doGet(request, response); //something went wrong - just refresh the Summary servlet 202 | } 203 | } 204 | } else { 205 | doGet(request, response); //something went wrong - just refresh the Summary servlet 206 | } 207 | } 208 | 209 | Broker[] getHardcodedBrokers() { 210 | Broker john = new Broker("John"); 211 | john.setTotal(1234.56); 212 | john.setLoyalty("Basic"); 213 | Broker karri = new Broker("Karri"); 214 | karri.setTotal(12345.67); 215 | karri.setLoyalty("Bronze"); 216 | Broker ryan = new Broker("Ryan"); 217 | ryan.setTotal(23456.78); 218 | ryan.setLoyalty("Bronze"); 219 | Broker raunak = new Broker("Raunak"); 220 | raunak.setTotal(98765.43); 221 | raunak.setLoyalty("Silver"); 222 | Broker greg = new Broker("Greg"); 223 | greg.setTotal(123456.78); 224 | greg.setLoyalty("Gold"); 225 | Broker eric = new Broker("Eric"); 226 | eric.setTotal(1234567.89); 227 | eric.setLoyalty("Platinum"); 228 | Broker[] brokers = { john, karri, ryan, raunak, greg, eric }; 229 | return brokers; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/Utilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2025 Kyndryl, All Rights Reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 18 | 19 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 20 | 21 | import com.ibm.cloud.objectstorage.ClientConfiguration; 22 | import com.ibm.cloud.objectstorage.auth.AWSCredentials; 23 | import com.ibm.cloud.objectstorage.auth.AWSStaticCredentialsProvider; 24 | import com.ibm.cloud.objectstorage.client.builder.AwsClientBuilder.EndpointConfiguration; 25 | import com.ibm.cloud.objectstorage.oauth.BasicIBMOAuthCredentials; 26 | 27 | //AWS S3 (wrapper for IBM Cloud Object Storage buckets) 28 | import com.ibm.cloud.objectstorage.services.s3.AmazonS3; 29 | import com.ibm.cloud.objectstorage.services.s3.AmazonS3ClientBuilder; 30 | 31 | //Having to use a proprietary WebSphere Liberty class here - ugh! 32 | //Stopped doing this - see https://github.com/OpenLiberty/open-liberty/issues/11225 33 | //import com.ibm.websphere.security.openidconnect.PropagationHelper; 34 | 35 | import java.io.PrintWriter; 36 | import java.io.StringWriter; 37 | import java.security.Principal; 38 | import java.util.logging.Level; 39 | import java.util.logging.Logger; 40 | import java.util.Base64; 41 | import java.util.HashMap; 42 | 43 | //Servlet 4.0 44 | import jakarta.servlet.http.HttpServletRequest; 45 | import jakarta.servlet.http.HttpSession; 46 | 47 | //mpJWT 1.0 48 | import org.eclipse.microprofile.jwt.JsonWebToken; 49 | 50 | 51 | public class Utilities { 52 | private static Logger logger = Logger.getLogger(Utilities.class.getName()); 53 | 54 | public static boolean useBasicAuth = false; 55 | public static boolean useOIDC = false; 56 | 57 | private static HashMap basicAuthCredentials = new HashMap(); 58 | 59 | private static boolean useS3 = false; 60 | private static AmazonS3 s3 = null; 61 | private static String s3Bucket = null; 62 | 63 | private static final String JWT = "jwt"; 64 | private static final String OIDC = "oidc"; 65 | private static final String BASIC_AUTH = "none"; 66 | 67 | 68 | // Override Broker Client URL if config map is configured to provide URL 69 | static { 70 | String authType = System.getenv("AUTH_TYPE"); 71 | logger.info("authType: "+authType); 72 | useOIDC = OIDC.equals(authType); 73 | useBasicAuth = BASIC_AUTH.equals(authType); 74 | 75 | useS3 = Boolean.parseBoolean(System.getenv("S3_ENABLED")); 76 | logger.info("useS3: "+useS3); 77 | 78 | String mpUrlPropName = BrokerClient.class.getName() + "/mp-rest/url"; 79 | String brokerURL = System.getenv("BROKER_URL"); 80 | if ((brokerURL != null) && !brokerURL.isEmpty()) { 81 | logger.info("Using Broker URL from config map: " + brokerURL); 82 | System.setProperty(mpUrlPropName, brokerURL); 83 | } else { 84 | logger.info("Broker URL not found from env var from config map, so defaulting to value in jvm.options: " 85 | + System.getProperty(mpUrlPropName)); 86 | } 87 | } 88 | 89 | public Utilities(Logger callerLogger) { 90 | logger = callerLogger; 91 | } 92 | 93 | void addBasicAuthCredentials(String user, String password) { 94 | String credentials = user + ":" + password; 95 | String authString = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()); 96 | basicAuthCredentials.put(user, authString); 97 | } 98 | 99 | String getAuthHeader(JsonWebToken jwt, HttpServletRequest request) { 100 | String authHeader = null; 101 | if (useBasicAuth) { 102 | Principal principal = request.getUserPrincipal(); 103 | if (principal!=null) { 104 | String user = principal.getName(); 105 | authHeader = basicAuthCredentials.get(user); 106 | if (authHeader==null) logger.warning("No credentials found for user: "+user); 107 | } else { 108 | logger.warning("No identity associated with request!"); 109 | } 110 | } else { 111 | authHeader = "Bearer "+getJWT(jwt, request); 112 | } 113 | logger.fine("authHeader = "+authHeader); 114 | return authHeader; 115 | } 116 | 117 | String getJWT(JsonWebToken jwt, HttpServletRequest request) { 118 | String token = null; 119 | 120 | // The below gets the JWT issued by Liberty itself (via the jwtSSO feature), not the JWT from your OIDC provider (such as KeyCloak) 121 | // See https://github.com/OpenLiberty/open-liberty/issues/11225 for details 122 | // if ("Bearer".equals(PropagationHelper.getAccessTokenType())) { 123 | // token = PropagationHelper.getIdToken().getAccessToken(); 124 | // logger.fine("Retrieved JWT provided through oidcClientConnect feature"); 125 | if (useOIDC) { 126 | HttpSession session = request.getSession(); //When multiple Trader pods exist, need to enable distributed session support 127 | if (session!=null) { 128 | token = (String) session.getAttribute(JWT); //Summary.doGet puts this here when redirected to after KeyCloak login 129 | if (token!= null) { 130 | logger.fine("Retrieved JWT from the session"); 131 | } else { 132 | logger.warning("Unable to retrieve JWT from the session"); 133 | } 134 | } else { 135 | logger.warning("Session was null"); 136 | } 137 | } else { 138 | token = jwt.getRawToken(); 139 | logger.fine("Retrieved JWT provided through CDI injected JsonWebToken"); 140 | } 141 | return token; 142 | } 143 | 144 | public static void logToS3(String key, Object document) { 145 | if (useS3) try { 146 | if (s3 == null) { 147 | logger.info("Initializing S3"); 148 | //Using System.getenv because can't use CDI injection of ConfigProperty from a static method 149 | String region = System.getenv("S3_REGION"); 150 | String apiKey = System.getenv("S3_API_KEY"); 151 | String serviceInstanceId = System.getenv("S3_SERVICE_INSTANCE_ID"); 152 | String endpointUrl = System.getenv("S3_ENDPOINT_URL"); 153 | String location = System.getenv("S3_LOCATION"); 154 | 155 | AWSCredentials credentials = new BasicIBMOAuthCredentials(apiKey, serviceInstanceId); 156 | ClientConfiguration clientConfig = new ClientConfiguration().withRequestTimeout(5000).withTcpKeepAlive(true); 157 | s3 = AmazonS3ClientBuilder.standard() 158 | .withCredentials(new AWSStaticCredentialsProvider(credentials)) 159 | .withEndpointConfiguration(new EndpointConfiguration(endpointUrl, location)) 160 | .withPathStyleAccessEnabled(true) 161 | .withClientConfiguration(clientConfig) 162 | .build(); 163 | 164 | s3Bucket = System.getenv("S3_BUCKET"); 165 | if (!s3.doesBucketExistV2(s3Bucket)) { 166 | logger.info("Creating S3 bucket: "+s3Bucket); 167 | s3.createBucket(s3Bucket); 168 | } 169 | } 170 | 171 | logger.fine("Putting object in S3 bucket for "+key); 172 | s3.putObject(s3Bucket, key, document.toString()); 173 | } catch (Throwable t) { 174 | logException(t); 175 | } 176 | } 177 | 178 | public static void logException(Throwable t) { 179 | logger.warning(t.getClass().getName()+": "+t.getMessage()); 180 | 181 | //only log the stack trace if the level has been set to at least INFO 182 | if (logger.isLoggable(Level.INFO)) { 183 | StringWriter writer = new StringWriter(); 184 | t.printStackTrace(new PrintWriter(writer)); 185 | logger.info(writer.toString()); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/ViewPortfolio.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2025 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Broker; 22 | 23 | import java.io.IOException; 24 | import java.math.RoundingMode; 25 | import java.text.NumberFormat; 26 | import java.util.Iterator; 27 | import java.util.logging.Logger; 28 | 29 | //CDI 1.2 30 | import jakarta.inject.Inject; 31 | import jakarta.enterprise.context.ApplicationScoped; 32 | 33 | //JSON-P 1.0 (JSR 353). The replaces my old usage of IBM's JSON4J (com.ibm.json.java.JSONObject) 34 | import jakarta.json.JsonObject; 35 | 36 | //Servlet 3.1 37 | import jakarta.servlet.ServletException; 38 | import jakarta.servlet.annotation.HttpConstraint; 39 | import jakarta.servlet.annotation.ServletSecurity; 40 | import jakarta.servlet.annotation.WebServlet; 41 | import jakarta.servlet.http.HttpServlet; 42 | import jakarta.servlet.http.HttpServletRequest; 43 | import jakarta.servlet.http.HttpServletResponse; 44 | import jakarta.servlet.RequestDispatcher; 45 | 46 | //mpJWT 1.0 47 | import org.eclipse.microprofile.jwt.JsonWebToken; 48 | 49 | //mpRestClient 1.0 50 | import org.eclipse.microprofile.rest.client.inject.RestClient; 51 | 52 | 53 | /** 54 | * Servlet implementation class ViewPortfolio 55 | */ 56 | @WebServlet(description = "View Portfolio servlet", urlPatterns = { "/viewPortfolio" }) 57 | @ServletSecurity(@HttpConstraint(rolesAllowed = { "StockTrader", "StockViewer" } )) 58 | @ApplicationScoped 59 | public class ViewPortfolio extends HttpServlet { 60 | private static final long serialVersionUID = 4815162342L; 61 | private static final double ERROR = -1; 62 | private static final String ERROR_STRING = "Error"; 63 | private static final String TRADE_STOCK = "Buy/Sell Stock"; 64 | private static final String FEEDBACK = "Submit Feedback"; 65 | 66 | private static Logger logger = Logger.getLogger(ViewPortfolio.class.getName()); 67 | 68 | private static Utilities utilities = null; 69 | private static NumberFormat currency = null; 70 | 71 | private @Inject @RestClient BrokerClient brokerClient; 72 | 73 | private @Inject JsonWebToken jwt; 74 | 75 | /** 76 | * @see HttpServlet#HttpServlet() 77 | */ 78 | public ViewPortfolio() { 79 | super(); 80 | 81 | if (utilities == null) { 82 | currency = NumberFormat.getNumberInstance(); 83 | currency.setMinimumFractionDigits(2); 84 | currency.setMaximumFractionDigits(2); 85 | currency.setRoundingMode(RoundingMode.HALF_UP); 86 | 87 | utilities = new Utilities(logger); 88 | } 89 | } 90 | 91 | /** 92 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 93 | */ 94 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 95 | String owner = request.getParameter("owner"); 96 | 97 | //JsonObject portfolio = PortfolioServices.getPortfolio(request, owner); 98 | Broker broker = brokerClient.getBroker(utilities.getAuthHeader(jwt, request), owner); 99 | 100 | JsonObject stocks = null; 101 | String returnOnInvestment = "Unknown"; 102 | 103 | try { 104 | stocks = broker.getStocks(); 105 | //request.setAttribute("rows", getTableRows(stocks)); 106 | request.setAttribute("broker", broker); 107 | } catch (NullPointerException npe) { 108 | utilities.logException(npe); 109 | } 110 | 111 | try { 112 | returnOnInvestment = brokerClient.getReturnOnInvestment(utilities.getAuthHeader(jwt, request), owner); 113 | request.setAttribute("returnOnInvestment", returnOnInvestment); 114 | } catch (Throwable t) { 115 | logger.info("Unable to obtain return on investment for "+owner); 116 | utilities.logException(t); 117 | } 118 | 119 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsps/viewPortfolio.jsp"); 120 | dispatcher.forward(request, response); 121 | } 122 | 123 | /** 124 | * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 125 | */ 126 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 127 | String owner = request.getParameter("owner"); 128 | String submit = request.getParameter("submit"); 129 | 130 | if (submit != null) { 131 | if (submit.equals(TRADE_STOCK)) { 132 | response.sendRedirect("addStock?owner="+owner+"&source=viewPortfolio"); //send control to the AddStock servlet 133 | } else if (submit.equals(FEEDBACK)) { 134 | response.sendRedirect("feedback?owner="+owner); //send control to the Feedback servlet 135 | } else { 136 | response.sendRedirect("summary"); //send control to the Summary servlet 137 | } 138 | } 139 | } 140 | 141 | private String getTableRows(JsonObject stocks) { 142 | StringBuffer rows = new StringBuffer(); 143 | 144 | if (stocks != null) { 145 | Iterator keys = stocks.keySet().iterator(); 146 | 147 | while (keys.hasNext()) { 148 | String key = keys.next(); 149 | JsonObject stock = stocks.getJsonObject(key); 150 | 151 | String symbol = stock.getString("symbol"); 152 | int shares = stock.getInt("shares"); 153 | double price = stock.getJsonNumber("price").doubleValue(); 154 | String date = stock.getString("date"); 155 | double total = stock.getJsonNumber("total").doubleValue(); 156 | double commission = stock.getJsonNumber("commission").doubleValue(); 157 | 158 | String formattedPrice = "$"+currency.format(price); 159 | String formattedTotal = "$"+currency.format(total); 160 | String formattedCommission = "$"+currency.format(commission); 161 | 162 | if (price == ERROR) { 163 | formattedPrice = ERROR_STRING; 164 | formattedTotal = ERROR_STRING; 165 | formattedCommission = ERROR_STRING; 166 | } 167 | 168 | 169 | } 170 | } 171 | 172 | return rows.toString(); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/client/BrokerClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.client; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Feedback; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.Broker; 22 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.json.WatsonInput; 23 | 24 | import jakarta.ws.rs.ApplicationPath; 25 | import jakarta.ws.rs.Consumes; 26 | import jakarta.ws.rs.DELETE; 27 | import jakarta.ws.rs.GET; 28 | import jakarta.ws.rs.POST; 29 | import jakarta.ws.rs.PUT; 30 | import jakarta.ws.rs.HeaderParam; 31 | import jakarta.ws.rs.PathParam; 32 | import jakarta.ws.rs.Produces; 33 | import jakarta.ws.rs.QueryParam; 34 | import jakarta.ws.rs.Path; 35 | import jakarta.ws.rs.core.MediaType; 36 | 37 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; 38 | 39 | 40 | @ApplicationPath("/") 41 | @Path("/") 42 | @RegisterRestClient 43 | /** mpRestClient "remote" interface for the Broker microservice */ 44 | public interface BrokerClient { 45 | @GET 46 | @Path("/") 47 | @Produces(MediaType.APPLICATION_JSON) 48 | public Broker[] getBrokers(@HeaderParam("Authorization") String jwt); 49 | 50 | @POST 51 | @Path("/{owner}") 52 | @Produces(MediaType.APPLICATION_JSON) 53 | public Broker createBroker(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner, @QueryParam("balance") double balance, @QueryParam("currency") String currency); 54 | 55 | @GET 56 | @Path("/{owner}") 57 | @Produces(MediaType.APPLICATION_JSON) 58 | public Broker getBroker(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner); 59 | 60 | @PUT 61 | @Path("/{owner}") 62 | @Produces(MediaType.APPLICATION_JSON) 63 | public Broker updateBroker(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner, @QueryParam("symbol") String symbol, @QueryParam("shares") int shares); 64 | 65 | @DELETE 66 | @Path("/{owner}") 67 | @Produces(MediaType.APPLICATION_JSON) 68 | public Broker deleteBroker(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner); 69 | 70 | @GET 71 | @Path("/{owner}/returns") 72 | @Produces(MediaType.TEXT_PLAIN) 73 | public String getReturnOnInvestment(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner); 74 | 75 | @POST 76 | @Path("/{owner}/feedback") 77 | @Consumes("application/json") 78 | @Produces(MediaType.APPLICATION_JSON) 79 | public Feedback submitFeedback(@HeaderParam("Authorization") String jwt, @PathParam("owner") String owner, WatsonInput input); 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/client/PortfolioServices.old: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 IBM Corp All Rights Reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.client; 18 | 19 | //JSON Web Token (JWT) construction 20 | import com.ibm.websphere.security.jwt.InvalidBuilderException; 21 | import com.ibm.websphere.security.jwt.JwtBuilder; 22 | import com.ibm.websphere.security.jwt.JwtToken; 23 | 24 | //Standard HTTP request classes. Maybe replace these with use of JAX-RS 2.0 client package instead... 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.io.OutputStream; 28 | import java.net.HttpURLConnection; 29 | import java.net.URL; 30 | import java.util.Enumeration; 31 | 32 | //CDI 1.2 33 | import javax.enterprise.context.RequestScoped; 34 | import javax.inject.Inject; 35 | 36 | //mpConfig 1.2 37 | import org.eclipse.microprofile.config.inject.ConfigProperty; 38 | 39 | //JSON-P (JSR 353). The replaces my old usage of IBM's JSON4J (package com.ibm.json.java) 40 | import javax.json.Json; 41 | import javax.json.JsonArray; 42 | import javax.json.JsonArrayBuilder; 43 | import javax.json.JsonObject; 44 | import javax.json.JsonStructure; 45 | 46 | //Servlet 3.1 (JSR 340) 47 | import javax.servlet.http.HttpServletRequest; 48 | 49 | 50 | @RequestScoped 51 | public class PortfolioServices { 52 | private static final String PORTFOLIO_SERVICE = "http://portfolio-service:9080/portfolio"; 53 | private static PortfolioServices singleton = null; 54 | 55 | private String jwtAudience = System.getenv("JWT_AUDIENCE"); //use mpConfig instead of this 56 | // private @Inject @ConfigProperty(name = "JWT_AUDIENCE") String jwtAudience; 57 | 58 | private String jwtIssuer = System.getenv("JWT_ISSUER"); //use mpConfig instead of this 59 | // private @Inject @ConfigProperty(name = "JWT_ISSUER") String jwtIssuer; 60 | 61 | 62 | private static PortfolioServices getSingleton() { 63 | if (singleton == null) singleton = new PortfolioServices(); 64 | return singleton; //CDI requires non-static methods 65 | } 66 | 67 | public static JsonArray getPortfolios(HttpServletRequest request) { 68 | JsonArray portfolios = null; 69 | 70 | try { 71 | portfolios = (JsonArray) invokeREST(request, "GET", PORTFOLIO_SERVICE, null); 72 | } catch (Throwable t) { 73 | t.printStackTrace(); 74 | 75 | //return an empty (but not null) array if anything went wrong 76 | JsonArrayBuilder builder = Json.createArrayBuilder(); 77 | portfolios = builder.build(); 78 | } 79 | 80 | return portfolios; 81 | } 82 | 83 | public static JsonObject getPortfolio(HttpServletRequest request, String owner) { 84 | JsonObject portfolio = null; 85 | 86 | try { 87 | portfolio = (JsonObject) invokeREST(request, "GET", PORTFOLIO_SERVICE+"/"+owner, null); 88 | } catch (Throwable t) { 89 | t.printStackTrace(); 90 | } 91 | 92 | return portfolio; 93 | } 94 | 95 | public static JsonObject createPortfolio(HttpServletRequest request, String owner) { 96 | JsonObject portfolio = null; 97 | 98 | try { 99 | portfolio = (JsonObject) invokeREST(request, "POST", PORTFOLIO_SERVICE+"/"+owner, null); 100 | } catch (Throwable t) { 101 | t.printStackTrace(); 102 | } 103 | 104 | return portfolio; 105 | } 106 | 107 | public static JsonObject updatePortfolio(HttpServletRequest request, String owner, String symbol, int shares) { 108 | JsonObject portfolio = null; 109 | 110 | try { 111 | String uri = PORTFOLIO_SERVICE+"/"+owner+"?symbol="+symbol+"&shares="+shares; 112 | portfolio = (JsonObject) invokeREST(request, "PUT", uri, null); 113 | } catch (Throwable t) { 114 | t.printStackTrace(); 115 | } 116 | 117 | return portfolio; 118 | } 119 | 120 | public static JsonObject deletePortfolio(HttpServletRequest request, String owner) { 121 | JsonObject portfolio = null; 122 | 123 | try { 124 | portfolio = (JsonObject) invokeREST(request, "DELETE", PORTFOLIO_SERVICE+"/"+owner, null); 125 | } catch (Throwable t) { 126 | t.printStackTrace(); 127 | } 128 | 129 | return portfolio; 130 | } 131 | 132 | public static JsonObject submitFeedback(HttpServletRequest request, String owner, JsonObject feedback) { 133 | JsonObject response = null; 134 | 135 | try { 136 | String text = feedback.toString(); 137 | response = (JsonObject) invokeREST(request, "POST", PORTFOLIO_SERVICE+"/"+owner+"/feedback", text); 138 | } catch (Throwable t) { 139 | t.printStackTrace(); 140 | } 141 | 142 | return response; 143 | } 144 | 145 | private static JsonStructure invokeREST(HttpServletRequest request, String verb, String uri, String payload) throws IOException { 146 | //Get the logged in user 147 | String userName = request.getUserPrincipal().getName(); 148 | if (userName == null) userName = "null"; 149 | 150 | System.out.println(verb+" "+uri+" called by "+ userName); 151 | 152 | URL url = new URL(uri); 153 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 154 | 155 | copyFromRequest(conn, request); //forward headers (including cookies) from inbound request 156 | 157 | conn.setRequestMethod(verb); 158 | conn.setRequestProperty("Content-Type", "application/json"); 159 | conn.setDoOutput(true); 160 | 161 | // add the JWT token to the authorization header. 162 | String jwtToken = getSingleton().createJWT(userName); 163 | conn.setRequestProperty("Authorization", "Bearer "+ jwtToken); 164 | 165 | if (payload != null) { 166 | OutputStream body = conn.getOutputStream(); 167 | body.write(payload.getBytes()); 168 | body.flush(); 169 | body.close(); 170 | } 171 | 172 | InputStream stream = conn.getInputStream(); 173 | 174 | // JSONObject json = JSONObject.parse(stream); //JSON4J 175 | JsonStructure json = Json.createReader(stream).read(); 176 | 177 | stream.close(); 178 | 179 | return json; //I use JsonStructure here so I can return a JsonObject or a JsonArray 180 | } 181 | 182 | /** 183 | * Create Json Web Token. 184 | * return: the base64 encoded and signed token. 185 | */ 186 | private String createJWT(String userName){ 187 | String jwtTokenString = null; 188 | 189 | try { 190 | // create() uses default settings. 191 | // For other settings, specify a JWTBuilder element in server.xml 192 | // and call create(builder id) 193 | JwtBuilder builder = JwtBuilder.create(); 194 | 195 | // Put the user info into a JWT Token 196 | builder.subject(userName); 197 | builder.claim("upn", userName); 198 | 199 | // Set the audience to our sample's value 200 | builder.claim("aud", jwtAudience); 201 | 202 | //builder.claim("groups", groups); 203 | 204 | //convention is the issuer is the url, but for demo portability a fixed value is used. 205 | //builder.claim("iss", request.getRequestURL().toString()); 206 | builder.claim("iss", jwtIssuer); 207 | 208 | JwtToken theToken = builder.buildJwt(); 209 | jwtTokenString = theToken.compact(); 210 | } catch (Exception e) { 211 | e.printStackTrace(); 212 | throw new RuntimeException(e); 213 | } 214 | 215 | return jwtTokenString; 216 | } 217 | 218 | //forward headers (including cookies) from inbound request 219 | private static void copyFromRequest(HttpURLConnection conn, HttpServletRequest request) { 220 | Enumeration headers = request.getHeaderNames(); 221 | if (headers != null) { 222 | while (headers.hasMoreElements()) { 223 | String headerName = headers.nextElement(); //"Authorization" and "Cookie" are especially important headers 224 | String headerValue = request.getHeader(headerName); 225 | conn.setRequestProperty(headerName, headerValue); //odd it's called request property here, rather than header... 226 | } 227 | } 228 | } 229 | 230 | // Something bizarre is happening in both minikube and in CFC, where the http URL 231 | // is getting changed to an https one, but it still contains port 80 rather than 443. 232 | // This doesn't happen when running outside of Kube, or when running in Armada. 233 | // Might be an artifact of the Ingress proxy (my free Armada account doesn't support 234 | // Ingress, so I have to use a worker's nodePort instead). 235 | // TODO: Implementing an ugly hack for now; need to revisit (or open bug report on Ingress) 236 | static String getRedirectWorkaround(HttpServletRequest request) { 237 | String workaroundURL = ""; 238 | 239 | String requestURL = request.getRequestURL().toString(); 240 | if (requestURL.startsWith("https:") && requestURL.contains(":80/")) { 241 | //we got redirected from http to https, but the port number didn't get updated! 242 | workaroundURL = requestURL.replace(":80/", ":443/"); 243 | 244 | //strip off the current servlet path - caller will append new path 245 | workaroundURL = workaroundURL.replace(request.getServletPath(), "/"); 246 | 247 | System.out.println("Correcting "+requestURL+" to "+workaroundURL); 248 | } 249 | return workaroundURL; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/health/LivenessProbe.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.health; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.Summary; 21 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.Utilities; 22 | 23 | //Logging (JSR 47) 24 | import java.util.logging.Logger; 25 | 26 | //CDI 2.0 27 | import jakarta.enterprise.context.ApplicationScoped; 28 | 29 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.Summary; 30 | 31 | //mpHealth 1.0 32 | import org.eclipse.microprofile.health.HealthCheck; 33 | import org.eclipse.microprofile.health.HealthCheckResponse; 34 | import org.eclipse.microprofile.health.HealthCheckResponseBuilder; 35 | import org.eclipse.microprofile.health.Liveness; 36 | 37 | 38 | @Liveness 39 | @ApplicationScoped 40 | /** Use mpHealth for liveness probe */ 41 | public class LivenessProbe implements HealthCheck { 42 | private static Logger logger = Logger.getLogger(LivenessProbe.class.getName()); 43 | private static String jwtAudience = System.getenv("JWT_AUDIENCE"); 44 | private static String jwtIssuer = System.getenv("JWT_ISSUER"); 45 | private static Utilities utilities = null; 46 | 47 | public LivenessProbe() { 48 | if (utilities == null) utilities = new Utilities(logger); 49 | } 50 | 51 | //mpHealth probe 52 | public HealthCheckResponse call() { 53 | HealthCheckResponse response = null; 54 | String message = "Live"; 55 | try { 56 | HealthCheckResponseBuilder builder = HealthCheckResponse.named("Trader"); 57 | 58 | if (Summary.IS_FAILED.get()) { //can't run without these env vars 59 | builder = builder.down(); 60 | message = Summary.message; 61 | logger.warning("Returning NOT live!"); 62 | } else { 63 | builder = builder.up(); 64 | logger.fine("Returning live!"); 65 | } 66 | 67 | builder = builder.withData("message", message); 68 | 69 | response = builder.build(); 70 | } catch (Throwable t) { 71 | logger.warning("Exception occurred during health check: "+t.getMessage()); 72 | utilities.logException(t); 73 | throw t; 74 | } 75 | 76 | return response; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/health/ReadinessProbe.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019-2021 IBM Corp All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.health; 19 | 20 | import com.ibm.hybrid.cloud.sample.stocktrader.trader.Utilities; 21 | 22 | //Logging (JSR 47) 23 | import java.util.logging.Logger; 24 | 25 | //CDI 2.0 26 | import jakarta.enterprise.context.ApplicationScoped; 27 | 28 | //mpHealth 1.0 29 | import org.eclipse.microprofile.health.HealthCheck; 30 | import org.eclipse.microprofile.health.HealthCheckResponse; 31 | import org.eclipse.microprofile.health.HealthCheckResponseBuilder; 32 | import org.eclipse.microprofile.health.Readiness; 33 | 34 | 35 | @Readiness 36 | @ApplicationScoped 37 | /** Use mpHealth for readiness probe */ 38 | public class ReadinessProbe implements HealthCheck { 39 | private static Logger logger = Logger.getLogger(ReadinessProbe.class.getName()); 40 | private static String jwtAudience = System.getenv("JWT_AUDIENCE"); 41 | private static String jwtIssuer = System.getenv("JWT_ISSUER"); 42 | private static Utilities utilities = null; 43 | 44 | public ReadinessProbe() { 45 | if (utilities == null) utilities = new Utilities(logger); 46 | } 47 | 48 | 49 | //mpHealth probe 50 | public HealthCheckResponse call() { 51 | HealthCheckResponse response = null; 52 | String message = "Ready"; 53 | try { 54 | HealthCheckResponseBuilder builder = HealthCheckResponse.named("Trader"); 55 | 56 | if ((jwtAudience==null) || (jwtIssuer==null)) { //can't run without these env vars 57 | builder = builder.down(); 58 | message = "JWT environment variables not set!"; 59 | logger.warning("Returning NOT ready!"); 60 | } else { 61 | builder = builder.up(); 62 | logger.fine("Returning ready!"); 63 | } 64 | 65 | builder = builder.withData("message", message); 66 | 67 | response = builder.build(); 68 | } catch (Throwable t) { 69 | logger.warning("Exception occurred during health check: "+t.getMessage()); 70 | utilities.logException(t); 71 | throw t; 72 | } 73 | 74 | return response; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/json/Broker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp, All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.json; 17 | 18 | import java.math.RoundingMode; 19 | import java.text.NumberFormat; 20 | import java.util.Iterator; 21 | 22 | //JSON-P 1.0 (JSR 353). This replaces my old usage of IBM's JSON4J (com.ibm.json.java.JSONObject) 23 | import jakarta.json.Json; 24 | import jakarta.json.JsonNumber; 25 | import jakarta.json.JsonObject; 26 | import jakarta.json.JsonObjectBuilder; 27 | 28 | 29 | /** JSON-B POJO class representing a Broker JSON object */ 30 | public class Broker { 31 | private String owner; 32 | private double total; 33 | private String loyalty; 34 | private double balance; 35 | private double commissions; 36 | private int free; 37 | private String sentiment; 38 | private double nextCommission; 39 | private double cashAccountBalance; 40 | private String cashAccountCurrency; 41 | private JsonObject stocks; 42 | private NumberFormat currency = null; 43 | private static double ERROR = -1.0; 44 | 45 | 46 | public Broker() { //default constructor 47 | } 48 | 49 | public Broker(String initialOwner) { //primary key constructor 50 | setOwner(initialOwner); 51 | } 52 | 53 | public Broker(String initialOwner, double initialTotal, String initialLoyalty, double initialBalance, 54 | double initialCommissions, int initialFree, String initialSentiment, double initialNextCommission) { 55 | setOwner(initialOwner); 56 | setTotal(initialTotal); 57 | setLoyalty(initialLoyalty); 58 | setBalance(initialBalance); 59 | setCommissions(initialCommissions); 60 | setFree(initialFree); 61 | setSentiment(initialSentiment); 62 | setNextCommission(initialNextCommission); 63 | } 64 | 65 | public String getOwner() { 66 | return owner; 67 | } 68 | 69 | public void setOwner(String newOwner) { 70 | owner = newOwner; 71 | } 72 | 73 | public double getTotal() { 74 | return total; 75 | } 76 | 77 | public void setTotal(double newTotal) { 78 | total = newTotal; 79 | } 80 | 81 | public String getLoyalty() { 82 | return loyalty; 83 | } 84 | 85 | public void setLoyalty(String newLoyalty) { 86 | loyalty = newLoyalty; 87 | } 88 | 89 | public double getBalance() { 90 | return balance; 91 | } 92 | 93 | public void setBalance(double newBalance) { 94 | balance = newBalance; 95 | } 96 | 97 | public double getCommissions() { 98 | return commissions; 99 | } 100 | 101 | public void setCommissions(double newCommissions) { 102 | commissions = newCommissions; 103 | } 104 | 105 | public int getFree() { 106 | return free; 107 | } 108 | 109 | public void setFree(int newFree) { 110 | free = newFree; 111 | } 112 | 113 | public String getSentiment() { 114 | return sentiment; 115 | } 116 | 117 | public void setSentiment(String newSentiment) { 118 | sentiment = newSentiment; 119 | } 120 | 121 | public double getNextCommission() { 122 | return nextCommission; 123 | } 124 | 125 | public void setNextCommission(double newNextCommission) { 126 | nextCommission = newNextCommission; 127 | } 128 | 129 | public double getCashAccountBalance() { 130 | return cashAccountBalance; 131 | } 132 | 133 | public void setCashAccountBalance(double newCashAccountBalance) { 134 | cashAccountBalance = newCashAccountBalance; 135 | } 136 | 137 | public String getCashAccountCurrency() { 138 | return cashAccountCurrency; 139 | } 140 | 141 | public void setCashAccountCurrency(String newCashAccountCurrency) { 142 | cashAccountCurrency = newCashAccountCurrency; 143 | } 144 | 145 | public JsonObject getStocks() { 146 | return stocks; 147 | } 148 | 149 | public void setStocks(JsonObject newStocks) { 150 | stocks = newStocks; 151 | } 152 | 153 | public void addStock(Stock newStock) { 154 | if (newStock != null) { 155 | String symbol = newStock.getSymbol(); 156 | if (symbol != null) { 157 | JsonObjectBuilder stocksBuilder = Json.createObjectBuilder(); 158 | 159 | if (stocks != null) { //JsonObject is immutable, so copy current "stocks" into new builder 160 | Iterator iter = stocks.keySet().iterator(); 161 | while (iter.hasNext()) { 162 | String key = iter.next(); 163 | JsonObject obj = stocks.getJsonObject(key); 164 | stocksBuilder.add(key, obj); 165 | } 166 | } 167 | 168 | //can only add a JSON-P object to a JSON-P object; can't add a JSON-B object. So converting... 169 | JsonObjectBuilder builder = Json.createObjectBuilder(); 170 | 171 | builder.add("symbol", symbol); 172 | builder.add("shares", newStock.getShares()); 173 | builder.add("commission", newStock.getCommission()); 174 | builder.add("price", newStock.getPrice()); 175 | builder.add("total", newStock.getTotal()); 176 | builder.add("date", newStock.getDate()); 177 | 178 | JsonObject stock = builder.build(); 179 | 180 | stocksBuilder.add(symbol, stock); //might be replacing an item; caller needs to do any merge (like updatePortfolio does) 181 | stocks = stocksBuilder.build(); 182 | } 183 | } 184 | } 185 | 186 | public boolean equals(Object obj) { 187 | boolean isEqual = false; 188 | if ((obj != null) && (obj instanceof Broker)) isEqual = toString().equals(obj.toString()); 189 | return isEqual; 190 | } 191 | 192 | public String toString() { 193 | if (currency == null) { 194 | currency = NumberFormat.getNumberInstance(); 195 | currency.setMinimumFractionDigits(2); 196 | currency.setMaximumFractionDigits(2); 197 | currency.setRoundingMode(RoundingMode.HALF_UP); 198 | } 199 | 200 | return "{\"owner\": \""+owner+"\", \"total\": "+currency.format(total)+", \"loyalty\": \""+loyalty 201 | +"\", \"balance\": "+currency.format(balance)+", \"commissions\": "+currency.format(commissions) 202 | +", \"free\": "+free+", \"nextCommission\": "+currency.format(nextCommission) 203 | +", \"sentiment\": \""+sentiment+"\", \"stocks\": "+(stocks!=null?getStocksJSON():"{}")+"}"; 204 | } 205 | 206 | private String getStocksJSON() { 207 | StringBuffer json = new StringBuffer(); 208 | Iterator keys = stocks.keySet().iterator(); 209 | 210 | boolean first = true; 211 | while (keys.hasNext()) { 212 | if (first) { 213 | json.append("{"); 214 | } else { 215 | json.append(", "); 216 | first = false; 217 | } 218 | String key = keys.next(); 219 | JsonObject stock = stocks.getJsonObject(key); 220 | 221 | String symbol = stock.getString("symbol"); 222 | int shares = stock.getInt("shares"); 223 | JsonNumber number = stock.getJsonNumber("price"); 224 | double price = (number != null) ? number.doubleValue() : ERROR; 225 | String date = stock.getString("date"); 226 | number = stock.getJsonNumber("total"); 227 | double totalValue = (number != null) ? number.doubleValue() : ERROR; 228 | number = stock.getJsonNumber("commission"); 229 | double commission = (number != null) ? number.doubleValue() : ERROR; 230 | 231 | json.append("\""+key+"\": {\"symbol\": \""+symbol+"\", \"shares\": "+shares+", \"price\": "+currency.format(price) 232 | +", \"date\": \""+date+"\", \"total\": "+currency.format(totalValue)+", \"commission\": "+currency.format(commission)+"}"); 233 | } 234 | 235 | return json.append("}").toString(); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/json/Feedback.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 IBM Corp All Rights Reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.json; 18 | 19 | /** JSON-B POJO class representing a Feedback JSON object */ 20 | public class Feedback { 21 | private String message; 22 | private int free; 23 | private String sentiment; 24 | 25 | 26 | public Feedback() { //default constructor 27 | } 28 | 29 | public Feedback(String initialMessage) { //primary key constructor 30 | setMessage(initialMessage); 31 | } 32 | 33 | public Feedback(String initialMessage, int initialFree, String initialSentiment) { 34 | setMessage(initialMessage); 35 | setFree(initialFree); 36 | setSentiment(initialSentiment); 37 | } 38 | 39 | public String getMessage() { 40 | return message; 41 | } 42 | 43 | public void setMessage(String newMessage) { 44 | message = newMessage; 45 | } 46 | 47 | public int getFree() { 48 | return free; 49 | } 50 | 51 | public void setFree(int newFree) { 52 | free = newFree; 53 | } 54 | 55 | public String getSentiment() { 56 | return sentiment; 57 | } 58 | 59 | public void setSentiment(String newSentiment) { 60 | sentiment = newSentiment; 61 | } 62 | 63 | public String toString() { 64 | return "{\"message\": \""+message+"\", \"free\": "+free+", \"sentiment\": \""+sentiment+"\"}"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/json/Stock.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 IBM Corp All Rights Reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.json; 18 | 19 | /** JSON-B POJO class representing a Stock JSON object */ 20 | public class Stock { 21 | private String symbol; 22 | private int shares; 23 | private double commission; 24 | private double price; 25 | private double total; 26 | private String date; 27 | 28 | 29 | public Stock() { //default constructor 30 | } 31 | 32 | public Stock(String initialSymbol) { //primary key constructor 33 | setSymbol(initialSymbol); 34 | } 35 | 36 | public Stock(String initialSymbol, int initialShares, double initialCommission, 37 | double initialPrice, double initialTotal, String initialDate) { 38 | setSymbol(initialSymbol); 39 | setShares(initialShares); 40 | setCommission(initialCommission); 41 | setPrice(initialPrice); 42 | setTotal(initialTotal); 43 | setDate(initialDate); 44 | } 45 | 46 | public String getSymbol() { 47 | return symbol; 48 | } 49 | 50 | public void setSymbol(String newSymbol) { 51 | symbol = newSymbol; 52 | } 53 | 54 | public int getShares() { 55 | return shares; 56 | } 57 | 58 | public void setShares(int newShares) { 59 | shares = newShares; 60 | } 61 | 62 | public double getCommission() { 63 | return commission; 64 | } 65 | 66 | public void setCommission(double newCommission) { 67 | commission = newCommission; 68 | } 69 | 70 | public double getPrice() { 71 | return price; 72 | } 73 | 74 | public void setPrice(double newPrice) { 75 | price = newPrice; 76 | } 77 | 78 | public double getTotal() { 79 | return total; 80 | } 81 | 82 | public void setTotal(double newTotal) { 83 | total = newTotal; 84 | } 85 | 86 | public String getDate() { 87 | return date; 88 | } 89 | 90 | public void setDate(String newDate) { 91 | date = newDate; 92 | } 93 | 94 | public String toString() { 95 | return "{\"symbol\": \""+symbol+"\", \"shares\": "+shares+", \"commission\": "+commission 96 | +", \"price\": "+price+", \"total\": "+total+", \"date\": \""+date+"\"}"; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/json/WatsonInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 IBM Corp All Rights Reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.json; 18 | 19 | /** JSON-B POJO class representing a Watson Tone Analyzer input JSON object */ 20 | public class WatsonInput { 21 | private String text; 22 | 23 | 24 | public WatsonInput() { //default constructor 25 | } 26 | 27 | public WatsonInput(String initialText) { //primary key constructor 28 | setText(initialText); 29 | } 30 | 31 | public String getText() { 32 | return text; 33 | } 34 | 35 | public void setText(String newText) { 36 | text = newText; 37 | } 38 | 39 | public String toString() { 40 | return "{\"text\": \""+text+"\"}"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/liberty/config/configDropins/defaults/keystore.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/main/liberty/config/includes/basic.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | jwtSso-1.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | admin 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/liberty/config/includes/ldap.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | ldapRegistry-3.0 17 | jwtSso-1.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/liberty/config/includes/none.xml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | jwtSso-1.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | admin 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/liberty/config/includes/oidc.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | openidConnectClient-1.0 18 | 19 | 20 | 26 | 27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/liberty/config/jvm.options: -------------------------------------------------------------------------------- 1 | -Dcom.ibm.hybrid.cloud.sample.stocktrader.trader.client.BrokerClient/mp-rest/url=http://broker-service:9080/broker 2 | -------------------------------------------------------------------------------- /src/main/liberty/config/jvmbx.options: -------------------------------------------------------------------------------- 1 | -javaagent:/opt/ibm/wlp/usr/servers/defaultServer/resources/javametrics-agent.jar 2 | -Dorg.osgi.framework.bootdelegation=com.ibm.tivoli.itcam.* 3 | -------------------------------------------------------------------------------- /src/main/liberty/config/resources/security/key.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/src/main/liberty/config/resources/security/key.p12 -------------------------------------------------------------------------------- /src/main/liberty/config/resources/security/trust.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/src/main/liberty/config/resources/security/trust.p12 -------------------------------------------------------------------------------- /src/main/liberty/config/server.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | microProfile-6.1 20 | pages-3.1 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/ibm-web-ext.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/addPortfolio.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | Stock Trader 6 | 7 | 8 | 9 | header image 10 |

11 | This account will receive a free $50 balance for commissions! 12 |

13 |

14 | 15 | 16 | 17 |
Owner:
Cash Account initial balance:
Cash Account currency:
49 |
50 | 51 |
52 |
53 | 54 | footer image 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/addStock.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | Stock Trader 6 | 7 | 8 | 9 | header image 10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 |
Owner:${param.owner}
Commission: 20 | ${commission}
Stock Symbol: 24 |
Number of Shares: 28 |
Buy Sell
35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | footer image 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/error.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | Stock Trader 6 | 7 | 8 | 9 | header image 10 |
11 |
12 |
13 | An error occurred during login. Please try again. 14 |
15 |
16 | 17 |
18 |
19 | 20 | footer image 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/login.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | IBM Stock Trader 6 | 7 | 8 | 9 | header image 10 |

11 | Login to IBM Stock Trader 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
Username:
Password:
23 |
24 | 25 |
26 |
27 | 28 | footer image 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/message.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | Stock Trader 6 | 7 | 8 | 9 | header image 10 |

11 | ${message} 12 |

13 |

14 | 15 |
16 |
17 | 18 | footer image 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/submitFeedback.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false"%> 2 | 3 | 4 | 5 | Stock Trader 6 | 7 | 8 | 9 | header image 10 |

11 | Please share your feedback on this tool! 12 |

13 |

14 | 15 |

16 | 17 | 18 |

19 |
20 | 21 | footer image 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/summary.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false" 2 | import="java.text.*,java.math.RoundingMode,com.ibm.hybrid.cloud.sample.stocktrader.trader.Utilities,com.ibm.hybrid.cloud.sample.stocktrader.trader.json.*"%> 3 | 4 | <%! 5 | static NumberFormat currency = NumberFormat.getNumberInstance(); 6 | static { 7 | 8 | currency.setMinimumFractionDigits(2); 9 | currency.setMaximumFractionDigits(2); 10 | currency.setRoundingMode(RoundingMode.HALF_UP); 11 | } 12 | %> 13 | 14 | 15 | 16 | 17 | Stock Trader 18 | 19 | 20 | 21 | header image 22 |
23 |
24 | <% 25 | if(request.getAttribute("error") != null && ((Boolean)request.getAttribute("error")).booleanValue() == true) { 26 | %> 27 | Error communicating with the Broker microservice: ${message} 28 |

29 | Please consult the trader, broker and portfolio pod logs for more details, or ask your administator for help. 30 |

31 | <% 32 | } else { 33 | %> 34 |

35 | Retrieve selected portfolio
36 | 37 | <% if(request.isUserInRole("StockTrader")) { %> 38 | Create a new portfolio
39 | Update selected portfolio (buy/sell stock)
40 | Delete selected portfolio
41 | <% } %> 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | <% 52 | Broker[] brokers = (Broker[])request.getAttribute("brokers"); 53 | 54 | if(brokers == null) { 55 | %> 56 | Error communicating with the Broker microservice: ${message} 57 |

58 | Please consult the trader, broker and portfolio pod logs for more details, or ask your administator for help. 59 |

60 | <% 61 | } else { 62 | 63 | for (int index=0; index 68 |

69 | 70 | 71 | 72 | 73 | 74 | <% 75 | } 76 | } 77 | %> 78 |
OwnerTotalLoyalty Level
><%=owner%>$<%=currency.format(broker.getTotal())%><%=broker.getLoyalty()%>
79 |
80 | 81 | 82 |
83 | <% 84 | } 85 | 86 | %> 87 | 88 |
89 | 90 | footer image 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsps/viewPortfolio.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" session="false" 2 | import="java.text.*,java.math.RoundingMode,com.ibm.hybrid.cloud.sample.stocktrader.trader.json.*,jakarta.json.*,java.util.*"%> 3 | 4 | <%! 5 | static NumberFormat currency = NumberFormat.getNumberInstance(); 6 | static { 7 | 8 | currency.setMinimumFractionDigits(2); 9 | currency.setMaximumFractionDigits(2); 10 | currency.setRoundingMode(RoundingMode.HALF_UP); 11 | } 12 | %> 13 | 14 | 15 | 16 | 17 | Stock Trader 18 | 19 | 20 | 21 | header image 22 |
23 |
24 |
25 | Stock Portfolio for ${param.owner}:
26 |
27 | <% 28 | Broker broker = ((Broker)request.getAttribute("broker")); 29 | if(broker == null ) { 30 | out.println("No broker data"); 31 | } 32 | else { 33 | %> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ${rows} 44 | <% 45 | JsonObject stocks = broker.getStocks(); 46 | if (stocks != null) { 47 | Iterator keys = stocks.keySet().iterator(); 48 | 49 | while (keys.hasNext()) { 50 | String key = keys.next(); 51 | JsonObject stock = stocks.getJsonObject(key); 52 | 53 | String symbol = stock.getString("symbol"); 54 | int shares = stock.getInt("shares"); 55 | double price = stock.getJsonNumber("price").doubleValue(); 56 | String date = stock.getString("date"); 57 | double total = stock.getJsonNumber("total").doubleValue(); 58 | double commission = stock.getJsonNumber("commission").doubleValue(); 59 | 60 | String formattedPrice = "$"+currency.format(price); 61 | String formattedTotal = "$"+currency.format(total); 62 | String formattedCommission = "$"+currency.format(commission); 63 | 64 | if (price == -1) { 65 | formattedPrice = "Error"; 66 | formattedTotal = "Error"; 67 | formattedCommission = "Error"; 68 | } 69 | %> 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | <% 79 | } 80 | } 81 | 82 | 83 | %> 84 |
SymbolSharesPriceDate QuotedTotalCommission
<%=symbol%><%=shares%><%=formattedPrice%><%=date%><%=formattedTotal%><%=formattedCommission%>
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
Portfolio Value:$<%=currency.format(broker.getTotal())%>
Loyalty Level:${broker.loyalty}
Account Balance:$<%=currency.format(broker.getBalance())%>
Cash Account Balance:<%=broker.getCashAccountCurrency()%> <%=currency.format(broker.getCashAccountBalance())%>
Total Commissions Paid:$<%=currency.format(broker.getCommissions())%>
Free Trades Available:${broker.free}
Sentiment:${broker.sentiment}
Return On Investment:${returnOnInvestment}
120 |
121 | 122 | 123 | 124 |
125 | <% } //else %> 126 |
127 | 128 | footer image 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Trader UI 4 | User Interface (UI) for the Stock Trader application 5 | 6 | 7 | FORM 8 | BasicRegistry 9 | 10 | /login 11 | /error 12 | 13 | 14 | 15 | 16 | /* 17 | 18 | 19 | ** 20 | 21 | 22 | 23 | 24 | unsecured content 25 | /index.html 26 | / 27 | /header.jpg 28 | /footer.jpg 29 | /health 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml.basicregistry: -------------------------------------------------------------------------------- 1 | 2 | 3 | Trader UI 4 | User Interface (UI) for the Stock Trader application 5 | 6 | 7 | FORM 8 | BasicRegistry 9 | 10 | /login 11 | /error 12 | 13 | 14 | 15 | 16 | /* 17 | 18 | 19 | ** 20 | 21 | 22 | 23 | 24 | unsecured content 25 | /index.html 26 | / 27 | /header.jpg 28 | /footer.jpg 29 | /health 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/webapp/footer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/src/main/webapp/footer.jpg -------------------------------------------------------------------------------- /src/main/webapp/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBMStockTrader/trader/a120b043cfb95c8c936f7a24eb963c1f476fb9fe/src/main/webapp/header.jpg -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirect to Trader UI 6 | 7 | 8 |

9 | If not automatically redirected, 10 | click here 11 | to redirect to the Trader UI. 12 |

13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/test/HealthEndpointIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp, All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.test; 19 | 20 | import static org.junit.Assert.assertTrue; 21 | 22 | import jakarta.ws.rs.client.Client; 23 | import jakarta.ws.rs.client.ClientBuilder; 24 | import jakarta.ws.rs.client.Invocation; 25 | import jakarta.ws.rs.core.Response; 26 | 27 | import org.junit.Test; 28 | 29 | public class HealthEndpointIT { 30 | 31 | private String port = System.getProperty("http.port"); 32 | // private String warContext = System.getProperty("war.name"); 33 | private String liveUrl = "http://localhost:" + port + "/health/live"; 34 | private String readyUrl = "http://localhost:" + port + "/health/ready"; 35 | // private String url = "http://localhost:" + port; 36 | private static final int MAX_RETRY_COUNT = 5; 37 | private static final int SLEEP_TIMEOUT = 3000; 38 | 39 | @Test 40 | public void testLiveEndpoint() throws Exception { 41 | 42 | System.out.println("Testing endpoint " + liveUrl ); 43 | int responseCode = makeRequest(liveUrl); 44 | for(int i = 0; (responseCode != 200) && (i < MAX_RETRY_COUNT); i++) { 45 | System.out.println("Response code : " + responseCode + ", retrying ... (" + i + " of " + MAX_RETRY_COUNT + ")"); 46 | Thread.sleep(SLEEP_TIMEOUT); 47 | responseCode = makeRequest(liveUrl); 48 | } 49 | assertTrue("Incorrect response code: " + responseCode, responseCode == 200); 50 | } 51 | 52 | @Test 53 | public void testReadyEndpoint() throws Exception { 54 | System.out.println("Testing endpoint " + readyUrl); 55 | int responseCode = makeRequest(readyUrl); 56 | for(int i = 0; (responseCode != 200) && (i < MAX_RETRY_COUNT); i++) { 57 | System.out.println("Response code : " + responseCode + ", retrying ... (" + i + " of " + MAX_RETRY_COUNT + ")"); 58 | Thread.sleep(SLEEP_TIMEOUT); 59 | responseCode = makeRequest(readyUrl); 60 | } 61 | assertTrue("Incorrect response code: " + responseCode, responseCode == 200); 62 | } 63 | 64 | private int makeRequest(String urlToTest) { 65 | Client client = ClientBuilder.newClient(); 66 | Invocation.Builder invoBuild = client.target(urlToTest).request(); 67 | Response response = invoBuild.get(); 68 | int responseCode = response.getStatus(); 69 | response.close(); 70 | return responseCode; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/ibm/hybrid/cloud/sample/stocktrader/trader/test/HomePageIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 IBM Corp, All Rights Reserved 3 | Copyright 2022-2024 Kyndryl, All Rights Reserved 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.ibm.hybrid.cloud.sample.stocktrader.trader.test; 19 | 20 | import static org.junit.Assert.assertTrue; 21 | 22 | import jakarta.ws.rs.client.Client; 23 | import jakarta.ws.rs.client.ClientBuilder; 24 | import jakarta.ws.rs.client.Invocation; 25 | import jakarta.ws.rs.core.Response; 26 | 27 | import org.junit.Test; 28 | 29 | public class HomePageIT { 30 | 31 | private String port = System.getProperty("http.port"); 32 | private String warContext = System.getProperty("war.name"); 33 | 34 | private String url = "http://localhost:" + port + "/" + warContext + "/login"; 35 | private static final int MAX_RETRY_COUNT = 5; 36 | private static final int SLEEP_TIMEOUT = 3000; 37 | 38 | @Test 39 | public void testHomeEndpoint() throws Exception { 40 | 41 | System.out.println("Testing endpoint " + url ); 42 | int responseCode = makeRequest(url); 43 | for(int i = 0; (responseCode != 200) && (i < MAX_RETRY_COUNT); i++) { 44 | System.out.println("Response code : " + responseCode + ", retrying ... (" + i + " of " + MAX_RETRY_COUNT + ")"); 45 | Thread.sleep(SLEEP_TIMEOUT); 46 | responseCode = makeRequest(url); 47 | } 48 | assertTrue("Incorrect response code: " + responseCode, responseCode == 200); 49 | } 50 | 51 | private int makeRequest(String urlToTest) { 52 | Client client = ClientBuilder.newClient(); 53 | Invocation.Builder invoBuild = client.target(urlToTest).request(); 54 | Response response = invoBuild.get(); 55 | int responseCode = response.getStatus(); 56 | response.close(); 57 | return responseCode; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /trigger.txt: -------------------------------------------------------------------------------- 1 | gha trigger 9 2 | rtclauss trigger 1 3 | --------------------------------------------------------------------------------