├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs ├── lab_bluegreen.md ├── lab_circuitbreaker.md ├── lab_configserver.md ├── lab_pushall.md ├── lab_pushquote.md ├── lab_registryserver.md ├── lab_scale.md ├── lab_setup.md ├── lab_userprovided.md ├── microservices_relationship.png ├── springtrader.png └── springtrader2.png ├── gradle ├── versioning.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── springboottrades-accounts ├── manifest-unversioned.yml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── pivotal │ │ │ └── accounts │ │ │ ├── AccountsApplication.java │ │ │ ├── config │ │ │ └── WebConfig.java │ │ │ ├── controller │ │ │ ├── AccountController.java │ │ │ └── AuthenticationController.java │ │ │ ├── domain │ │ │ ├── Account.java │ │ │ └── AuthenticationRequest.java │ │ │ ├── exception │ │ │ ├── AuthenticationException.java │ │ │ └── NoRecordsFoundException.java │ │ │ ├── repository │ │ │ └── AccountRepository.java │ │ │ └── service │ │ │ └── AccountService.java │ └── resources │ │ └── bootstrap.yml │ └── test │ └── java │ └── io │ └── pivotal │ └── accounts │ ├── AccountsApplicationTest.java │ ├── configuration │ └── ServiceTestConfiguration.java │ ├── controller │ ├── AccountsControllerTest.java │ └── AuthenticationControllerTest.java │ └── service │ └── AccountServiceTest.java ├── springboottrades-portfolio ├── manifest-unversioned.yml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── pivotal │ │ │ └── portfolio │ │ │ ├── PortfolioApplication.java │ │ │ ├── controller │ │ │ └── PortfolioController.java │ │ │ ├── domain │ │ │ ├── Holding.java │ │ │ ├── Order.java │ │ │ ├── OrderType.java │ │ │ ├── Portfolio.java │ │ │ └── Quote.java │ │ │ ├── repository │ │ │ └── OrderRepository.java │ │ │ └── service │ │ │ ├── PortfolioService.java │ │ │ └── QuoteRemoteCallService.java │ └── resources │ │ └── bootstrap.yml │ └── test │ ├── java │ └── io │ │ └── pivotal │ │ └── portfolio │ │ ├── PortfolioApplicationTest.java │ │ ├── config │ │ └── ServiceTestConfiguration.java │ │ ├── controller │ │ └── PortfolioControllerTest.java │ │ └── service │ │ ├── PortfolioServiceTest.java │ │ └── QuoteRemoteCallServiceTest.java │ └── resources │ └── application.properties ├── springboottrades-quotes ├── manifest-unversioned.yml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── pivotal │ │ │ └── quotes │ │ │ ├── QuotesApplication.java │ │ │ ├── controller │ │ │ └── QuoteController.java │ │ │ ├── domain │ │ │ ├── CompanyInfo.java │ │ │ ├── Quote.java │ │ │ ├── QuoteMapper.java │ │ │ ├── Security.java │ │ │ ├── YahooQuote.java │ │ │ ├── YahooQuoteList.java │ │ │ ├── YahooQuoteResponses.java │ │ │ └── YahooResults.java │ │ │ ├── exception │ │ │ └── SymbolNotFoundException.java │ │ │ └── service │ │ │ └── QuoteService.java │ └── resources │ │ └── bootstrap.yml │ └── test │ ├── java │ └── io │ │ └── pivotal │ │ └── quotes │ │ ├── QuotesApplicationTest.java │ │ ├── configuration │ │ └── TestConfiguration.java │ │ ├── controller │ │ └── QuoteControllerTest.java │ │ └── service │ │ └── QuoteServiceTest.java │ └── resources │ └── application-test.yml └── springboottrades-web ├── manifest-unversioned.yml └── src ├── main ├── java │ └── io │ │ └── pivotal │ │ └── web │ │ ├── WebApplication.java │ │ ├── config │ │ ├── MvcConfig.java │ │ ├── ThymeleafConfig.java │ │ └── WebSecurityConfig.java │ │ ├── controller │ │ ├── PortfolioController.java │ │ ├── TradeController.java │ │ └── UserController.java │ │ ├── domain │ │ ├── Account.java │ │ ├── AuthenticationRequest.java │ │ ├── CompanyInfo.java │ │ ├── Holding.java │ │ ├── MarketSummary.java │ │ ├── Order.java │ │ ├── OrderType.java │ │ ├── Portfolio.java │ │ ├── Quote.java │ │ └── Search.java │ │ ├── exception │ │ └── OrderNotSavedException.java │ │ ├── security │ │ ├── CustomAuthenticationProvider.java │ │ ├── CustomCredentialsService.java │ │ └── LogoutSuccessHandler.java │ │ └── service │ │ ├── MarketService.java │ │ ├── MarketSummaryService.java │ │ └── UserService.java └── resources │ ├── bootstrap.yml │ ├── static │ ├── css │ │ └── style.css │ ├── fonts │ │ ├── colaborate-bold-webfont.eot │ │ ├── colaborate-bold-webfont.svg │ │ ├── colaborate-bold-webfont.ttf │ │ ├── colaborate-bold-webfont.woff │ │ ├── colaborate-light-webfont.eot │ │ ├── colaborate-light-webfont.svg │ │ ├── colaborate-light-webfont.ttf │ │ ├── colaborate-light-webfont.woff │ │ ├── colaborate-medium-webfont.eot │ │ ├── colaborate-medium-webfont.svg │ │ ├── colaborate-medium-webfont.ttf │ │ ├── colaborate-medium-webfont.woff │ │ ├── colabreg-webfont.eot │ │ ├── colabreg-webfont.svg │ │ ├── colabreg-webfont.ttf │ │ ├── colabreg-webfont.woff │ │ ├── colabthi-webfont.eot │ │ ├── colabthi-webfont.svg │ │ ├── colabthi-webfont.ttf │ │ └── colabthi-webfont.woff │ └── images │ │ ├── bg-container.gif │ │ ├── bg-small.gif │ │ ├── border-bg.jpg │ │ ├── button-transactions.png │ │ ├── glyphicons-halflings-white.png │ │ ├── icon-arrow.png │ │ ├── icon-custom.png │ │ └── logo.png │ └── templates │ ├── error.html │ ├── fragments │ ├── account_fragment.html │ ├── header_fragment.html │ ├── login_fragment.html │ ├── marketsummary_fragment.html │ ├── navbar_fragment.html │ ├── portfolio_fragment.html │ ├── registration_fragment.html │ └── trade_fragment.html │ ├── index.html │ ├── portfolio.html │ ├── registration.html │ └── trade.html └── test └── java └── io └── pivotal └── web ├── WebApplicationTests.java └── service └── MarketServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | *.classpath 4 | *.project 5 | *.settings/ 6 | *.ipr 7 | *.iws 8 | -------------------------------------------------------------------------------- /docs/lab_bluegreen.md: -------------------------------------------------------------------------------- 1 | # Zero downtime deployments. 2 | 3 | One of the benefits of a microservice architecture is the ability to upgrade each service individually. However, for continuous operations, we would like no downtime during the upgrade as well as the ability to test the new version and be able to rollback in case the new version doesn't *cut the mustard*. This can be accomplished with the platform. 4 | 5 | In this lab, you will be mimicking upgrading the quote service. 6 | 7 | ### Exercise 8 | 9 | 1. Create a new manifest file to deploy the quote service. Give the application a different name and a different route. 10 | 11 | 2. Push the application. 12 | > What happens in the registry service UI after you pushed the application? 13 | 14 | 3. Use the CLI or UI to delete the old version of the quote service whilst making requests on the UI of the application. 15 | 16 | 17 | # Blue/green deployments. 18 | 19 | In the [Creating the discovery service](lab_registryserver.md) lab we discussed an alternative approach for microservices to bind to each other. In this approach, we cannot rely on the registry service to give us the zero downtime deployments. Even in the registry service approach, we may want to upgrade the registry server itself in which case we can employ the Blue/Green deployment technique. 20 | 21 | In order to minimise downtime as new versions of the software is created and released, **Cloud Foundry** provides a technique to deploy these new versions/releases without incurring downtime. This technique is called [Blue/Green deployment](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/blue-green.html) 22 | 23 | In fact, the technique is very powerful, as it allows multiple versions of the application to be deployed and either serving requests or not serving requests (or even serving requests but not on the production route.) 24 | 25 | The procedure is very simple: 26 | 27 | * push the new version of the application with a new name and a new route. 28 | * test the new version under the new route. 29 | * if successful, bind the old route or production route to the new version. 30 | * unbind the route from the old version. 31 | * the old version can remain running until it is certain a rollback will not be necessary and then removed. 32 | -------------------------------------------------------------------------------- /docs/lab_circuitbreaker.md: -------------------------------------------------------------------------------- 1 | # Creating the Circuit Breaker Dashboard. 2 | 3 | Cloud-native architectures are typically composed of multiple layers of distributed services. End-user requests may comprise multiple calls to these services, and if a lower-level service fails, the failure can cascade up to the end user and spread to other dependent services. Heavy traffic to a failing service can also make it difficult to repair. Using Circuit Breaker Dashboard, you can monitor a service for failure, prevent failures from cascading, and supply dependent services until the failing service is operable again. 4 | 5 | The code to create a circuit breaker involved providing a fallback method. For example: 6 | 7 | ```java 8 | @HystrixCommand(fallbackMethod = "getCompaniesFallback") 9 | public List getCompanies(String name) { 10 | logger.debug("Fetching companies with name or symbol matching: " + name); 11 | CompanyInfo[] infos = restTemplate.getForObject("http://quotes/company/{name}", CompanyInfo[].class, name); 12 | return Arrays.asList(infos); 13 | } 14 | 15 | private List getCompaniesFallback(String name) { 16 | List infos = new ArrayList<>(); 17 | return infos; 18 | } 19 | ``` 20 | It is useful to know when the circuits are open as it may suggest a problem with the dependent services. [Spring Cloud Services for Pivotal Cloud Foundry](https://network.pivotal.io/products/p-spring-cloud-services) provides a centralised dashboard to collect all the statistics from circuit breakers and visualize them in one place. 21 | 22 | Underneath the covers, this circuit breaker pattern is implemented using the [Spring Cloud Netflix - Hystrix](http://cloud.spring.io/spring-cloud-netflix/). 23 | 24 | In order to make use of the dashboard, we need to create an instance of the service to bind it to our applications. 25 | ### Exercise 26 | 27 | 1. Log in to the Apps Manager through your browser. The URL will be: `https://console./` 28 | 29 | Go the *Marketplace* and choose a *Circuit Breaker for Pivotal Cloud Foundry standard*. 30 | 31 | When prompted for the name of the service, insert **"circuit-breaker-dashboard"** and bind it to the space you are using to deploy your applications. 32 | 33 | > You can pick any name of the service, however, the service is already specified in the manifest files, so it is easier to re-use that name. If you do modify the name, ensure you modify it in the manifest files as well. 34 | 35 | ##Deploying without Spring Cloud Services 36 | If the cloud does not provide us with the services, then we can deploy the services ourselves. Bare in mind that our deployment of the Circuit Breaker Dashboard Service will not be highly available or load balanced. 37 | 38 | Follow the guidelines to deploy the Discover service [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras) - TODO! 39 | 40 | Currently, this service is not available in the [extras](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras) project but will be soon. However, the service is not required to run the application. Just ensure you remove the service name from the manifest files. 41 | 42 | In order for our microservices to be able to connect to the Circuit breaker dashboard service, we will have to create a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html). This tells our microservices where to find the service. 43 | 44 | ### Exercise 45 | 1. Create a *user provided service* using the CLI. 46 | 47 | Name this service **circuit-breaker-dashboard** and specify the URI of your instance of the registry service. For example: 48 | 49 | `cf cups circuit-breaker-dashboard -p '{"tag":"eureka","uri":""}'` 50 | 51 | The URI of your discovery service is the URI where your dashboard service is deployed. This was displayed at the end of `cf push` command when deploying the discovery service. 52 | > The URI will be similar to `eureka-dpinto.cfapps.io`. 53 | 54 | > Do **not** specify the protocol!!! ie. "http://" 55 | 56 | ##Running it locally 57 | If you want to run all the services locally, you'll need to start the circuit breaker dashboard service. 58 | 59 | Follow the guidelines to run the Discover service locally [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras). 60 | 61 | # Summary 62 | 63 | The circuit breaker dashboard can be accessed on the link provided in the console UI. Find the circuit breaker service you created and click on **Manage**. 64 | 65 | You can now move on to [creating the configuration service](lab_configserver.md) 66 | -------------------------------------------------------------------------------- /docs/lab_configserver.md: -------------------------------------------------------------------------------- 1 | # Creating the Configuration service. 2 | 3 | All our microservices will retrieve their configuration from a Configuration service. We will use the Configuration service provided by the [Spring Cloud Services for PCF](https://network.pivotal.io/products/p-spring-cloud-services) if available. There are [notes below](#Deploying without Spring Cloud Services) on how to create this service in case[Spring Cloud Services for PCF](https://network.pivotal.io/products/p-spring-cloud-services) is not available in your cloud or you want to run it [locally](#Running it locally). 4 | 5 | Underneath the covers, this discovery service is implemented using the [Spring Cloud Config](http://cloud.spring.io/spring-cloud-config/). 6 | 7 | ### Exercise 8 | 9 | 1. Log in to the Apps Manager through your browser. The URL will be: `https://console./` 10 | 11 | Go the *Marketplace* and choose a *Config Server for Pivotal Cloud Foundry*. 12 | 13 | When prompted for the name of the service, insert **"config-server"** and bind it to the space you are using to deploy your applications. 14 | 15 | > You can pick any name of the service, however, the service is already specified in the manifest files, so it is easier to re-use that name. If you do modify the name, ensure you modify it in the manifest files as well. 16 | 17 | 2. Click on *Manage* for the service you created to open the service dashboard. It will prompt you to enter either a Git or Subversion URI. Choose Git and enter **https://github.com/dpinto-pivotal/cf-SpringBootTrader-config.git** as the URI. 18 | 19 | ##Deploying without Spring Cloud Services 20 | If the cloud does not provide us with the services, then we can deploy the services ourselves. Bare in mind that our deployment of the Config Service will not be highly available or load balanced. 21 | 22 | Follow the guidelines to deploy the Config service [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras). 23 | 24 | In order for our microservices to be able to connect to the Registry service, we will have to create a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html). This tells our microservices where to find the registry service. 25 | 26 | ### Exercise 27 | 1. Create a *user provided service* using the CLI. 28 | 29 | Name this service **config-server** and specify the URI of your instance of the registry service. For example: 30 | 31 | `cf cups config-service -p '{"tag":"config","uri":""}'` 32 | 33 | The URI of your Config server is the URI where your config server is deployed. This was displayed at the end of `cf push` command when deploying the service. 34 | 35 | 2. Multiple spaces. 36 | 37 | If you are deploying the services to multiple [spaces](http://docs.pivotal.io/pivotalcf/concepts/roles.html#spaces), then you must create the user-provided service in each space. 38 | 39 | ##Running it locally 40 | If you want to run all the services locally, you'll need to start the discovery service. 41 | 42 | Follow the guidelines to run the Discover service locally [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras). 43 | 44 | You can now move on to [pushing the quote service](lab_pushquote.md) 45 | -------------------------------------------------------------------------------- /docs/lab_pushall.md: -------------------------------------------------------------------------------- 1 | # Pushing all the services. 2 | 3 | In this exercise, we will be deploying all the applications in the project to the cloud and create all required services. 4 | 5 | ### 1. Quote service 6 | We have already deployed the quote service in the [previous lab](lab_pushquote.md), so nothing to do here. 7 | 8 | ### 2. Accounts service 9 | The accounts service has a dependency on a RDBMS database. 10 | 11 | **Cloud Foundry** provides a marketplace where administrators can enable certain services to be available to developers/operators. These services are called [*Managed services*](http://docs.pivotal.io/pivotalcf/devguide/services/#managed-services), in contrast to [*User-provided services*](http://docs.pivotal.io/pivotalcf/devguide/services/#user-provided-services). 12 | 13 | There are a couple of ways to create a service in **Cloud Foundry**. For this service we will explore using the UI to create the service, but you can also create it using the CLI. 14 | 15 | ### Exercise 16 | 17 | 1. Log in to the Apps Manager through your browser. The URL will be: `https://console./` 18 | 19 | Go the *Marketplace* and choose a MySQL service. In Pivotal Web Services we could use the "ClearDB MySQL Database" service. In Pivotal Cloud Foundry we can use the "MySQL for Pivotal Cloud Foundry" service. Depending on your cloud provider, you may have multiple plans to choose from. For this exercise, the smallest *free* plan will suffice. 20 | 21 | When prompted for the name of the service, insert **"traderdb"** and bind it to the space you are using to deploy your applications. 22 | 23 | > You can pick any name of the service, however, the service is already specified in the manifest files, so it is easier to re-use that name. If you do modify the name, ensure you modify it in the manifest files as well. 24 | 25 | ## 3. Portfolio service 26 | 27 | The portfolio service has a dependency on 3 services: 28 | 29 | - A RDBMS. 30 | - The quote service. 31 | - The Account service. 32 | 33 | For the RDBMS, we will be re-using the service created for the *Accounts service*. This is for simplicity of these exercises, and it is possible and probably favorable to create a new DB service specific to the portfolio service. 34 | 35 | The portfolio service also connects to the quote and account service. We are using a registry service to automatically and dynamically discover the other services. The other services are discovered automatically use the discovery service. 36 | 37 | ## 4. Web service 38 | The Web service is the UI front-end and also acts as an API aggregator. As such, it uses all the other microservices in the project, i.e. The quote, account and portfolio services. 39 | 40 | Similarly to above, we will be using the registry service to retrieve information about these microservices. 41 | 42 | 43 | ## 5. Push all the applications. 44 | 45 | Now that we have all the required services created, let's push all the services. 46 | 47 | ### Exercises 48 | 49 | 1. Push each of the services to the platform. 50 | 51 | > How could you push all the services in one go? 52 | > The **Cloud Foundry** manifest file allows us to [define multiple applications in a single file](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/manifest.html#multi-apps) 53 | 54 | 2. When the script has finished, set the `CF_TARGET` environment variable in both applications to the API endpoint of your Elastic Runtime instance (as in `https://api.example.com`), then restage the applications so that the changes will take effect. 55 | 56 | ``` 57 | $ cf set-env accounts CF_TARGET https://api.cloudfoundry.com 58 | Setting env variable 'CF_TARGET' to 'https://api.cloudfoundry.com' for app accounts in org myorg / space outer as user... 59 | OK 60 | TIP: Use 'cf restage' to ensure your env variable changes take effect 61 | $ cf restage accounts 62 | ``` 63 | > You only need to do this once per application. 64 | 65 | Once completed, go to the URL of the Web service in your browser. 66 | 67 | ##Deploying without Spring Cloud Services 68 | 69 | If Spring Cloud Services are not available, you should have pushed an instance of the [discovery service](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras) to the cloud and you should already have a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html). 70 | 71 | Thus all you'll need to do is push the applications as above. Ensure you create the **traderdb** RDBMS service. 72 | 73 | ### Exercise 74 | 1. push the applications 75 | 76 | ##Running it locally 77 | To run the service locally, you can use the gradle wrapper script as such: 78 | 79 | ``` 80 | gradlew :springboottrades-:bootRun 81 | ``` 82 | The services should start up and bind to the discovery service running locally. 83 | 84 | 85 | # Summary 86 | Congratulations! You have now deployed a set of microservices to the cloud that interact with each other. 87 | 88 | Feel free to familiarise yourself with the UI of the application. You can access the application in a browser on the URL provided at the end of the push command and see something similar to the image below. 89 | 90 | ![Spring Trader](/docs/springtrader.png) 91 | 92 | Now you can go to [next lab](lab_scale.md) 93 | -------------------------------------------------------------------------------- /docs/lab_pushquote.md: -------------------------------------------------------------------------------- 1 | # Pushing the Quote service 2 | 3 | In **Cloud Foundry** vocabulary, deploying an application is referred to as *pushing* the application since we are uploading the application artifact to the cloud. 4 | 5 | More information on the application deployment process can be found [here](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/deploy-app.html) 6 | 7 | The `cf push` command is used for this. In fact, running this command with no parameters, the CLI will look for a file *manifest.yml* in the current directory. Alternatively, you can specify an application name as the parameter and the CLI will upload all files it finds in the current directory. 8 | 9 | ### Application manifest files. 10 | Application manifests tell `cf push` what to do with applications. This includes everything from how many instances to create and how much memory to allocate to what services applications should use. 11 | 12 | A manifest can help you automate deployment, especially of multiple applications at once. 13 | 14 | ## Modifying the manifest files. 15 | Since each application requires a route to be bounded to the application, and potentially there may be many instances of these services deployed, we want to ensure we have unique routes bounded to the services we deploy. 16 | 17 | Luckily, Pivotal Cloud Foundry allows us to assign a [`random-route`](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/manifest.html#random-route) to applications. The manifest files in the project include this option. 18 | 19 | ### Exercise 20 | 21 | 1. *Push* the **quote service** to the cloud by [specifying the specific manifest file](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/manifest.html#find-manifest) to the `cf push` command. 22 | 23 | > HINT: user the -f option to the push command. 24 | 25 | > Manifest files are generated as part of the build. They are placed in `springboottrades-quotes/build` 26 | 27 | 2. When the script has finished, set the `CF_TARGET` environment variable in both applications to the API endpoint of your Elastic Runtime instance (as in `https://api.example.com`), then restage the applications so that the changes will take effect. 28 | 29 | ``` 30 | $ cf set-env quotes CF_TARGET https://api.cloudfoundry.com 31 | Setting env variable 'CF_TARGET' to 'https://api.cloudfoundry.com' for app quotes in org myorg / space outer as user... 32 | OK 33 | TIP: Use 'cf restage' to ensure your env variable changes take effect 34 | $ cf restage quotes 35 | ``` 36 | > You only need to do this once per application. 37 | 38 | > IMPORTANT: The Starters for Spring Cloud Services has a dependency on Spring Security, and by default, this will cause all client application endpoints to be protected by HTTP Basic authentication. You can disable this; see ["Disable HTTP Basic Authentication"](http://docs.pivotal.io/spring-cloud-services/service-registry/registering-a-service.html#disable-http-basic-auth) in the [Spring Cloud Services documentation for Service Registry](http://docs.pivotal.io/spring-cloud-services/service-registry/). 39 | 40 | ##Deploying without Spring Cloud Services 41 | 42 | If Spring Cloud Services are not available, you should have pushed an instance of the [discovery service](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras) to the cloud. Now, you'll have to create a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html) and bind it to the quote service. 43 | 44 | ### Exercise 45 | 1. Create a *user provided service* using the CLI. 46 | 47 | Name this service **circuit-breaker-dashboard** and specify the URI of your instance of the registry service. For example: 48 | 49 | `cf cups discovery-service -p '{"tag":"eureka","uri":""}'` 50 | 51 | The URI of your discovery service is the URI where your dashboard service is deployed. This was displayed at the end of `cf push` command when deploying the discovery service. 52 | > The URI will be similar to `eureka-dpinto.cfapps.io`. 53 | 54 | > Do **not** specify the protocol!!! ie. "http://" 55 | 56 | 2. push the application 57 | 58 | ##Running it locally 59 | To run the quote service locally, you can use the gradle wrapper script as such: 60 | 61 | ``` 62 | gradlew :springboottrades-quotes:bootRun 63 | ``` 64 | The service should start up and bind to the discovery service running locally. 65 | 66 | # Summary 67 | 68 | Ensure you have a working quote service application by sending HTTP requests to it, for example: 69 | 70 | `curl http:///quotes?q=EMC` 71 | 72 | > You can also put the above URL in your browser. 73 | 74 | Ensure the service registers itself with the registry server by looking at the discovery service dashboard. 75 | 76 | Congratulations! you have now deployed an application to the cloud that registers itself with the registry service and handles HTTP requests. 77 | 78 | Now you can go to [next lab](lab_pushall.md) 79 | -------------------------------------------------------------------------------- /docs/lab_registryserver.md: -------------------------------------------------------------------------------- 1 | # Creating the discovery service. 2 | 3 | All our microservices will connect to a Service Registry. We will use the Discovery service provided by the [Spring Cloud Services for PCF](https://network.pivotal.io/products/p-spring-cloud-services) if available. There are [notes below](#Deploying without Spring Cloud Services) on how to create this service if [Spring Cloud Services for PCF](https://network.pivotal.io/products/p-spring-cloud-services) is not available in your cloud or you want to run it [locally](#Running it locally). 4 | 5 | Underneath the covers, this discovery service is implemented using the [Spring Cloud Netflix - Eureka](http://cloud.spring.io/spring-cloud-netflix/). 6 | 7 | As such, all we have to do to implement a discovery service is to create a service instance. 8 | ### Exercise 9 | 10 | 1. Log in to the Apps Manager through your browser. The URL will be: `https://console./` 11 | 12 | Go the *Marketplace* and choose a *Service Registry for Pivotal Cloud Foundry*. 13 | 14 | When prompted for the name of the service, insert **"discovery-service"** and bind it to the space you are using to deploy your applications. 15 | 16 | > You can pick any name of the service, however, the service is already specified in the manifest files, so it is easier to re-use that name. If you do modify the name, ensure you modify it in the manifest files as well. 17 | 18 | ##Deploying without Spring Cloud Services 19 | If the cloud does not provide us with the services, then we can deploy the services ourselves. Bare in mind that our deployment of the Discovery Service will not be highly available or load balanced. 20 | 21 | Follow the guidelines to deploy the Discover service [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras). 22 | 23 | In order for our microservices to be able to connect to the Registry service, we will have to create a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html). This tells our microservices where to find the registry service. 24 | 25 | ### Exercise 26 | 1. Create a *user provided service* using the CLI. 27 | 28 | Name this service **discovery-service** and specify the URI of your instance of the registry service. For example: 29 | 30 | `cf cups discovery-service -p '{"tag":"eureka","uri":""}'` 31 | 32 | The URI of your discovery service is the URI where your discovery service is deployed. This was displayed at the end of `cf push` command when deploying the discovery service. 33 | > The URI will be similar to `eureka-dpinto.cfapps.io`. 34 | 35 | > Do **not** specify the protocol!!! ie. "http://" 36 | 37 | 2. Multiple spaces. 38 | 39 | If you are deploying the services to multiple [spaces](http://docs.pivotal.io/pivotalcf/concepts/roles.html#spaces), then you must create the user-provided service in each space. 40 | 41 | ##Running it locally 42 | If you want to run all the services locally, you'll need to start the discovery service. 43 | 44 | Follow the guidelines to run the Discover service locally [here](https://github.com/dpinto-pivotal/cf-SpringBootTrader-extras). 45 | 46 | ## Alternative to Registry service 47 | The registry service allows us to have a dynamic registration and discovery of services. However, it is not the only way for microservices to connect to each other. 48 | 49 | An alternative is to use [Spring Cloud Connectors](http://cloud.spring.io/spring-cloud-connectors/) with [user-provided or managed services](http://docs.pivotal.io/pivotalcf/devguide/services/). 50 | 51 | This repository has a [branch](https://github.com/dpinto-pivotal/cf-SpringBootTrader/tree/v0.1-CUPS-based) with a version of the application that utilises user-provided services to discover each other. 52 | 53 | It is also possible to deploy your own Eureka server to the platform. 54 | 55 | # Summary 56 | 57 | The registry server has a UI that can be accessed on the link provided in the console UI. Find the discovery service you created and click on **Manage**. 58 | 59 | If you deployed your own Discovery service or are running it locally, you can also access the UI by going to the deployed application's URL. 60 | 61 | You can now move on to [Creating the Circuit Breaker Dashboard](lab_circuitbreaker.md) 62 | -------------------------------------------------------------------------------- /docs/lab_scale.md: -------------------------------------------------------------------------------- 1 | # Scaling the services. 2 | Within **Cloud Foundry** the concept of scalability can mean different things: 3 | - scaling the amount of resources available to the platform to run applications instances - as well as other platform components - [Scaling CF](http://docs.pivotal.io/pivotalcf/concepts/high-availability.html). 4 | - increasing memory of each application instance ([vertical scaling](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/cf-scale.html#vertical)). 5 | - horizontally scaling the application ([horizontal scaling](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/cf-scale.html#horizontal)). 6 | 7 | One of the benefits of a micro service architecture is that each service can be scaled independently. This means that if the quote service is under stress (CPU,memory), it can be scaled up independently from the other services. The platform will automatically load balance the requests across the available instances. 8 | 9 | ### Exercise 10 | 1. Scale the Quote service to two instances using either the UI or the CLI. 11 | 12 | 2. Monitor the logs of the quote service application, either via the [CLI](http://docs.pivotal.io/pivotalcf/devguide/deploy-apps/streaming-logs.html#view) or the UI. 13 | 14 | 3. Use the REST api of the quote service to retrieve quotes, for example: 15 | 16 | `curl http:///quotes?q=EMC` 17 | 18 | or put the URL in your browser window. 19 | 20 | How do the logs show which instance of the application is actually servicing the request? 21 | 22 | How far can you scale the quote service? what is the limitation? 23 | 24 | # Summary 25 | 26 | What happened in the registry server when you scale up? 27 | 28 | Move on to the [next lab](lab_bluegreen.md) 29 | -------------------------------------------------------------------------------- /docs/lab_setup.md: -------------------------------------------------------------------------------- 1 | #Setting up the environment. 2 | In this exercise, we will be setting up the environment and all the tools required. 3 | 4 | It assumes you are running Linux or MacOS, although it is possible to configure on Windows too. 5 | 6 | If you are in a Pivotal workshop and don't want to install and configure these tools in your laptop, ask for the VM which has all required tools already installed. However, you'll still have to provide credentials/accounts for git and Pivotal Web Services. 7 | 8 | ## 1. Java 9 | 10 | You'll need Java 8. 11 | 12 | ## 2. git install 13 | 14 | You'll need the git CLI in order to clone and use the code in this repository. 15 | 16 | Follow the instructions [here](https://help.github.com/articles/set-up-git/#platform-mac) to install and configure the git CLI on your host. 17 | 18 | ## 3. Cloud Foundry account creation 19 | 20 | You'll need an account on a **Cloud Foundry** instance. If you are in a Pivotal workshop, you can skip this step, if not, Pivotal provides a public instance of **Cloud Foundry** with a 60 day free trial period [here](http://run.pivotal.io). 21 | 22 | 23 | ## 4. Orgs and spaces 24 | 25 | In order to deploy applications to **Cloud Foundry**, you'll need to setup an organisation and a space. If you created the account on Pivotal Web Services, these should have been setup automatically. If you are using another instance of **CF**, then check you have at least one organisation and one space - this is the minimum required for the labs. 26 | 27 | You can learn more about orgs and spaces within **Cloud Foundry** at http://docs.pivotal.io/pivotalcf/concepts/roles.html 28 | 29 | ## 5. Cloud Foundry CLI 30 | 31 | The **Cloud Foundry** command line interface is an easy way to interact with instances of **Cloud Foundry**. 32 | 33 | You can obtain the CLI for multiple OS [here](https://github.com/cloudfoundry/cli) 34 | 35 | 36 | ## 6. Cloning the repository 37 | 38 | Clone the GIT repository to your local machine. On the command line issue the following command: 39 | 40 | ```git clone https://github.com/dpinto-pivotal/cf-SpringBootTrader.git``` 41 | 42 | This command will copy the code in the repository to your local machine, creating a directory named `cf-SpringBootTrader`. All actions will now be done inside this directory. 43 | 44 | Once you have cloned the repository, it is important to build it to create the application artifacts. 45 | 46 | The projects use [gradle](http://gradle.org) as the build tool with gradle wrapper. Thus, all it is required to build all the microservices is: 47 | 48 | ``` 49 | ./gradlew build 50 | ``` 51 | 52 | You can also build individual services by naming them, for example: 53 | ``` 54 | ./gradlew :springboottrades-quotes:build 55 | ``` 56 | 57 | 58 | ## 7. Login to Cloud Foundry 59 | 60 | Login to your instance of **Cloud Foundry**. Instructions on how to do this can be found at http://docs.pivotal.io/pivotalcf/devguide/installcf/whats-new-v6.html#login 61 | 62 | # Summary 63 | 64 | At the end of this lab, you should have an environment setup to enable you to deploy applications to **Cloud Foundry**. You will also have the code required for the rest of the labs. 65 | 66 | In order to check that all is setup correctly, you should have something similar to the following: 67 | 68 | ``` 69 | Penguin:cf-SpringBootTrader dpinto$ cf target 70 | 71 | API endpoint: https://CF-URI (API version: 2.23.0) 72 | User: dpinto 73 | Org: dpinto-org 74 | Space: development 75 | ``` 76 | 77 | Now you can go to [next lab](lab_registryserver.md) 78 | -------------------------------------------------------------------------------- /docs/lab_userprovided.md: -------------------------------------------------------------------------------- 1 | # Creating a user-provided service 2 | In the previous lab, you deployed a single microservice that has no dependencies. However, it is very rare that a microservice has no dependencies - *no microservice is an island!* 3 | 4 | **Cloud Foundry** provides a marketplace where administrators can enable certain services to be available to developers/operators. These services are called [*Managed services*](http://docs.pivotal.io/pivotalcf/devguide/services/#managed-services), in contrast to [*User-provided services*](http://docs.pivotal.io/pivotalcf/devguide/services/#user-provided-services). We will be using a *Managed Service* in a later lab. 5 | 6 | In order for our microservices to be able to connect to the Registry service, we will have to create a [*User-provided service*](http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html). This tells our microservices where to find the registry service. 7 | 8 | ### Exercise 9 | 1. Create a *user provided service* using the CLI. 10 | 11 | Name this service **eureka-service** and specify the URI of your instance of the registry service. For example: 12 | 13 | `cf cups eureka-service -p '{"tag":"eureka","uri":""}'` 14 | 15 | The URI of your eureka service is the URI where your registry server is deployed. This was displayed at the end of `cf push` command in the [previous lab](lab_registryserver.md). 16 | > The URI will be similar to `eureka-dpinto.cfapps.io`. 17 | 18 | > Do **not** specify the protocol!!! ie. "http://" 19 | 20 | 2. Multiple spaces. 21 | 22 | If you are deploying the services to multiple [spaces](http://docs.pivotal.io/pivotalcf/concepts/roles.html#spaces), then you must create the user-provided service in each space. 23 | 24 | 25 | # Summary 26 | You have now created a *user-provided service*. This will allow other applications to know where your registry service is located so they can register themselves as well as discover where other services are located. 27 | 28 | Move on to [pushing the quote service lab](lab_pushquote.md). 29 | -------------------------------------------------------------------------------- /docs/microservices_relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/docs/microservices_relationship.png -------------------------------------------------------------------------------- /docs/springtrader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/docs/springtrader.png -------------------------------------------------------------------------------- /docs/springtrader2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/docs/springtrader2.png -------------------------------------------------------------------------------- /gradle/versioning.gradle: -------------------------------------------------------------------------------- 1 | // Sets version variable. 2 | // Major is hardcoded. 3 | // Minor is harcoded. 4 | // build is taken from environment variable "SOURCE_BUILD_NUMBER" 5 | 6 | version = new ProjectVersion() 7 | 8 | class ProjectVersion { 9 | String build 10 | ProjectVersion() { 11 | def file = new File('version/number') 12 | if(file.exists()) { 13 | this.build = file.text.trim() 14 | } else { 15 | this.build = "0.3.2" 16 | } 17 | } 18 | @Override 19 | String toString() { 20 | build 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 23 08:07:56 CST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include "springboottrades-accounts", "springboottrades-quotes", "springboottrades-web", "springboottrades-portfolio" 2 | -------------------------------------------------------------------------------- /springboottrades-accounts/manifest-unversioned.yml: -------------------------------------------------------------------------------- 1 | --- 2 | timeout: 180 3 | instances: 1 4 | memory: 512M 5 | env: 6 | SPRING_PROFILES_ACTIVE: cloud 7 | JAVA_OPTS: -Djava.security.egd=file:///dev/urandom 8 | JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: { memory_sizes: { metaspace: 100m }}]' 9 | applications: 10 | - name: accounts 11 | random-route: true 12 | path: libs/accounts-${version}.jar 13 | services: [ traderdb, discovery-service, circuit-breaker-dashboard, config-server ] 14 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/AccountsApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.accounts; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.sleuth.Sampler; 8 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler; 9 | import org.springframework.context.annotation.Bean; 10 | /** 11 | * Microservice to manage user accounts. 12 | * 13 | * Spring Boot application to provide a service to manage user accounts. 14 | * The application registers with a registry service - Eureka. 15 | * 16 | * @author David Ferreira Pinto 17 | * 18 | */ 19 | @SpringBootApplication 20 | @EnableDiscoveryClient 21 | @EnableCircuitBreaker 22 | public class AccountsApplication { 23 | 24 | @Bean 25 | public Sampler defaultSampler() { 26 | return new AlwaysSampler(); 27 | } 28 | 29 | public static void main(String[] args) { 30 | SpringApplication.run(AccountsApplication.class, args); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 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 | package io.pivotal.accounts.config; 17 | 18 | import java.text.SimpleDateFormat; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | import org.springframework.context.annotation.ComponentScan; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.http.MediaType; 25 | import org.springframework.http.converter.HttpMessageConverter; 26 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 27 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 28 | 29 | /** 30 | * Java configuration which bootstraps the web application context. Global error 31 | * handling is configured via 32 | * {@code configureHandlerExceptionResolvers(List 33 | * exceptionResolvers)} enabling consistent REST exception handling across 34 | * Controllers. 35 | * 36 | * 37 | * @author David Ferreira Pinto 38 | */ 39 | 40 | @Configuration 41 | @ComponentScan(basePackages = { "io.pivotal.accounts" }) 42 | public class WebConfig extends WebMvcConfigurationSupport { 43 | 44 | /** 45 | * configure the message converters with the date formatter. 46 | */ 47 | @Override 48 | public void configureMessageConverters( 49 | List> converters) { 50 | // Configure JSON support 51 | MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter(); 52 | mappingJacksonHttpMessageConverter.setSupportedMediaTypes(Arrays 53 | .asList(MediaType.APPLICATION_JSON)); 54 | //mappingJacksonHttpMessageConverter.getObjectMapper().configure( 55 | // Feature.WRITE_DATES_AS_TIMESTAMPS, true); 56 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:SS"); 57 | // There is no need to set the timezone as Jackson uses GMT and not the 58 | // local time zone (which is exactly what you want) 59 | // Note: While SimpleDateFormat is not threadsafe, Jackson Marshaller's 60 | // StdSerializerProvider clones the configured formatter for each thread 61 | mappingJacksonHttpMessageConverter.getObjectMapper().setDateFormat( 62 | format); 63 | //mappingJacksonHttpMessageConverter.getObjectMapper().configure( 64 | // Feature.INDENT_OUTPUT, true); 65 | // mappingJacksonHttpMessageConverter.getObjectMapper().getSerializationConfig().setSerializationInclusion(Inclusion.NON_NULL); 66 | converters.add(mappingJacksonHttpMessageConverter); 67 | } 68 | /* 69 | public void configureDefaultServletHandling( 70 | DefaultServletHandlerConfigurer configurer) { 71 | configurer.enable(); 72 | } 73 | 74 | @Override 75 | public void configureHandlerExceptionResolvers( 76 | List exceptionResolvers) { 77 | ExtendedExceptionHandlerExceptionResolver customResolver = new ExtendedExceptionHandlerExceptionResolver(); 78 | customResolver.setExceptionHandler(new GlobalExceptionHandler()); 79 | customResolver.setMessageConverters(getMessageConverters()); 80 | customResolver.afterPropertiesSet(); 81 | exceptionResolvers.add(customResolver); 82 | } 83 | */ 84 | } 85 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/controller/AuthenticationController.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.accounts.controller; 2 | 3 | import io.pivotal.accounts.domain.AuthenticationRequest; 4 | import io.pivotal.accounts.service.AccountService; 5 | 6 | import java.util.Map; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | //import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | import org.springframework.web.bind.annotation.ResponseStatus; 19 | import org.springframework.web.bind.annotation.RestController; 20 | /** 21 | * REST controller for the accounts microservice. 22 | * Provides the following endpoints: 23 | *

    24 | *
  • POST /login login request. 25 | *
  • GET /logout/{userId} logs out the account with given user id. 26 | *

27 | * @author David Ferreira Pinto 28 | * 29 | */ 30 | @RestController 31 | public class AuthenticationController { 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(AuthenticationController.class); 34 | /** 35 | * the service to delegate to. 36 | */ 37 | @Autowired 38 | private AccountService service; 39 | 40 | /** 41 | * Logins in the user from the authentication request passed in body. 42 | * 43 | * @param authenticationRequest The request with username and password. 44 | * @return HTTP status CREATED if successful. 45 | */ 46 | @RequestMapping(value = "/login", method = RequestMethod.POST) 47 | @ResponseStatus( HttpStatus.CREATED ) 48 | @ResponseBody 49 | public Map login(@RequestBody AuthenticationRequest authenticationRequest) { 50 | logger.debug("AuthenticationController.login: login request for username: " + authenticationRequest.getUsername()); 51 | Map authenticationResponse = this.service.login(authenticationRequest.getUsername(), authenticationRequest.getPassword()); 52 | return authenticationResponse;// authToken and accountId; 53 | } 54 | 55 | /** 56 | * Logs out the user. 57 | * 58 | * @param userId The user id to log out. 59 | */ 60 | @RequestMapping(value = "/logout/{user}", method = RequestMethod.GET) 61 | @ResponseStatus( HttpStatus.OK ) 62 | @ResponseBody 63 | public void logout(@PathVariable("user") final String userId) { 64 | logger.debug("AuthenticationController.logout: logout request for userid: " + userId); 65 | this.service.logout(userId); 66 | } 67 | 68 | /** 69 | * To ensure no one does login through HTTP GET. 70 | * returns METHOD_NOT_ALLOWED. 71 | */ 72 | @RequestMapping(value = "/login", method = RequestMethod.GET) 73 | @ResponseStatus( HttpStatus.METHOD_NOT_ALLOWED ) 74 | public void get() { 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/domain/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 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 | package io.pivotal.accounts.domain; 17 | 18 | /** 19 | * Represents an authentication request object. 20 | * 21 | * Used to make login requests. 22 | * 23 | * @author David Ferreira Pinto 24 | */ 25 | public class AuthenticationRequest { 26 | private String username; 27 | private String password; 28 | 29 | public String getUsername() { 30 | return username; 31 | } 32 | 33 | public void setUsername(String username) { 34 | this.username = username; 35 | } 36 | 37 | public String getPassword() { 38 | return password; 39 | } 40 | 41 | public void setPassword(String password) { 42 | this.password = password; 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | final int prime = 31; 48 | int result = 1; 49 | result = prime * result + ((password == null) ? 0 : password.hashCode()); 50 | result = prime * result + ((username == null) ? 0 : username.hashCode()); 51 | return result; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object obj) { 56 | if (this == obj) 57 | return true; 58 | if (obj == null) 59 | return false; 60 | if (getClass() != obj.getClass()) 61 | return false; 62 | AuthenticationRequest other = (AuthenticationRequest) obj; 63 | if (password == null) { 64 | if (other.password != null) 65 | return false; 66 | } else if (!password.equals(other.password)) 67 | return false; 68 | if (username == null) { 69 | if (other.username != null) 70 | return false; 71 | } else if (!username.equals(other.username)) 72 | return false; 73 | return true; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "AuthenticationRequest [username=" + username + ", password=" + password + "]"; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 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 | package io.pivotal.accounts.exception; 17 | 18 | /** 19 | * AuthenticationException should be thrown whenever a login attempt fails to 20 | * find the user 21 | * 22 | * @author Brian Dussault 23 | */ 24 | 25 | @SuppressWarnings("serial") 26 | public class AuthenticationException extends RuntimeException { 27 | 28 | public AuthenticationException(String message) { 29 | super(message); 30 | } 31 | } -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/exception/NoRecordsFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 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 | package io.pivotal.accounts.exception; 17 | 18 | /** 19 | * NoRecordsFoundException should be thrown whenever an entity is not 20 | * found via the repositories finder method. This exception is thrown from the controller 21 | * 22 | * @author Brian Dussault 23 | */ 24 | 25 | @SuppressWarnings("serial") 26 | public class NoRecordsFoundException extends RuntimeException { 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/java/io/pivotal/accounts/repository/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.accounts.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import io.pivotal.accounts.domain.Account; 6 | 7 | public interface AccountRepository extends CrudRepository { 8 | public Account findByUseridAndPasswd(String userId, String passwd); 9 | public Account findByUserid(String userId); 10 | public Account findByAuthtoken(String authtoken); 11 | } 12 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles.active: local 3 | application: 4 | name: accounts-service 5 | info: 6 | build: 7 | group: ${group} 8 | name: ${name} 9 | description: ${description} 10 | version: ${version} -------------------------------------------------------------------------------- /springboottrades-accounts/src/test/java/io/pivotal/accounts/AccountsApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.accounts; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.SpringApplicationConfiguration; 6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 7 | import org.springframework.test.context.web.WebAppConfiguration; 8 | 9 | /** 10 | * Tests for the Accounts Application. 11 | * @author David Ferreira Pinto 12 | * 13 | */ 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringApplicationConfiguration(classes = AccountsApplication.class) 16 | @WebAppConfiguration 17 | public class AccountsApplicationTest { 18 | /** 19 | * test loading of spring context. 20 | */ 21 | @Test 22 | public void contextLoads() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /springboottrades-accounts/src/test/java/io/pivotal/accounts/controller/AuthenticationControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.accounts.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import io.pivotal.accounts.configuration.ServiceTestConfiguration; 8 | import io.pivotal.accounts.domain.AuthenticationRequest; 9 | import io.pivotal.accounts.exception.AuthenticationException; 10 | 11 | import io.pivotal.accounts.service.AccountService; 12 | 13 | import org.junit.Before; 14 | import org.junit.Ignore; 15 | import org.junit.Test; 16 | import org.mockito.InjectMocks; 17 | import org.mockito.Mock; 18 | import org.mockito.MockitoAnnotations; 19 | 20 | import static org.junit.Assert.assertNull; 21 | import static org.mockito.Mockito.when; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 24 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 28 | 29 | import org.springframework.http.MediaType; 30 | import org.springframework.test.web.servlet.MockMvc; 31 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 32 | 33 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 34 | import com.fasterxml.jackson.databind.ObjectMapper; 35 | 36 | public class AuthenticationControllerTest { 37 | 38 | private static String API_ROLE = "API_USER"; 39 | 40 | MockMvc mockMvc; 41 | 42 | @InjectMocks 43 | AuthenticationController controller; 44 | 45 | @Mock 46 | AccountService service; 47 | 48 | @Before 49 | public void setup() { 50 | MockitoAnnotations.initMocks(this); 51 | 52 | this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); 53 | } 54 | @Test 55 | public void doLoginGet() throws Exception { 56 | mockMvc.perform(get("/login")).andExpect(status().isMethodNotAllowed()); 57 | } 58 | 59 | @Test 60 | public void doLoginPost() throws Exception { 61 | when(service.login(ServiceTestConfiguration.USER_ID, ServiceTestConfiguration.PASSWORD)).thenReturn(ServiceTestConfiguration.loginResponse()); 62 | 63 | AuthenticationRequest request = new AuthenticationRequest(); 64 | request.setPassword(ServiceTestConfiguration.PASSWORD); 65 | request.setUsername(ServiceTestConfiguration.USER_ID); 66 | 67 | mockMvc.perform(post("/login").contentType(MediaType.APPLICATION_JSON).content(convertObjectToJson(request))) 68 | .andExpect(status().isCreated()) 69 | .andExpect(jsonPath("$.authToken").value(ServiceTestConfiguration.AUTH_TOKEN)) 70 | .andExpect(jsonPath("$.accountid").value(ServiceTestConfiguration.PROFILE_ID.intValue())) 71 | .andDo(print()); 72 | } 73 | @Test(expected=org.springframework.web.util.NestedServletException.class) 74 | public void doLoginPostBadPassword() throws Exception { 75 | when(service.login(ServiceTestConfiguration.USER_ID, ServiceTestConfiguration.BAD_PASSWORD)).thenThrow(new AuthenticationException("Login failed for user: " 76 | + ServiceTestConfiguration.USER_ID)); 77 | 78 | AuthenticationRequest request = new AuthenticationRequest(); 79 | request.setPassword(ServiceTestConfiguration.BAD_PASSWORD); 80 | request.setUsername(ServiceTestConfiguration.USER_ID); 81 | 82 | mockMvc.perform(post("/login").contentType(MediaType.APPLICATION_JSON).content(convertObjectToJson(request))) 83 | .andExpect(status().isCreated()) 84 | .andDo(print()); 85 | } 86 | 87 | @Test 88 | public void doLogoutPostNoUser() throws Exception { 89 | mockMvc.perform(post("/logout")) 90 | .andExpect(status().isNotFound()) 91 | .andDo(print()); 92 | } 93 | @Test 94 | public void doLogoutGet() throws Exception { 95 | mockMvc.perform(get("/logout/"+ServiceTestConfiguration.USER_ID)) 96 | .andExpect(status().isOk()) 97 | .andDo(print()); 98 | } 99 | 100 | 101 | @Test 102 | public void doLogoutGetNoUser() throws Exception { 103 | /*Collection grantedAuthorities = new ArrayList(); 104 | grantedAuthorities.add(new SimpleGrantedAuthority(API_ROLE)); 105 | UserDetails user = new CustomUser(ServiceTestConfiguration.USER_ID, 106 | ServiceTestConfiguration.PASSWORD, grantedAuthorities, 107 | ServiceTestConfiguration.PROFILE_ID, 108 | ServiceTestConfiguration.AUTH_TOKEN); 109 | Authentication authentication = new TestingAuthenticationToken(user, 110 | ServiceTestConfiguration.PASSWORD, 111 | (List) grantedAuthorities); 112 | SecurityContextHolder.getContext().setAuthentication(authentication); 113 | */ 114 | mockMvc.perform(get("/logout")) 115 | .andExpect(status().isNotFound()) 116 | .andDo(print()); 117 | 118 | //assertNull(SecurityContextHolder.getContext().getAuthentication().getPrincipal()); 119 | } 120 | 121 | private byte[] convertObjectToJson(AuthenticationRequest request) throws Exception{ 122 | ObjectMapper mapper = new ObjectMapper(); 123 | mapper.setSerializationInclusion(Include.NON_NULL); 124 | return mapper.writeValueAsBytes(request); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /springboottrades-portfolio/manifest-unversioned.yml: -------------------------------------------------------------------------------- 1 | --- 2 | timeout: 180 3 | instances: 1 4 | memory: 512M 5 | env: 6 | SPRING_PROFILES_ACTIVE: cloud 7 | JAVA_OPTS: -Djava.security.egd=file:///dev/urandom 8 | JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: { memory_sizes: { metaspace: 100m }}]' 9 | applications: 10 | - name: portfolio 11 | random-route: true 12 | path: libs/portfolio-${version}.jar 13 | services: [ traderdb, discovery-service, circuit-breaker-dashboard, config-server ] 14 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/PortfolioApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.sleuth.Sampler; 8 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 11 | /** 12 | * SpringBoot application for the portfolio microservice. 13 | * 14 | * Responsible for managing the portfolio as well as providing the API. 15 | * 16 | * @author David Ferreira Pinto 17 | * 18 | */ 19 | @SpringBootApplication 20 | @EnableJpaRepositories 21 | @EnableDiscoveryClient 22 | @EnableCircuitBreaker 23 | public class PortfolioApplication { 24 | 25 | @Bean 26 | public Sampler defaultSampler() { 27 | return new AlwaysSampler(); 28 | } 29 | 30 | public static void main(String[] args) { 31 | SpringApplication.run(PortfolioApplication.class, args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/controller/PortfolioController.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.controller; 2 | 3 | import io.pivotal.portfolio.domain.Order; 4 | import io.pivotal.portfolio.domain.Portfolio; 5 | import io.pivotal.portfolio.service.PortfolioService; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.RestController; 18 | import org.springframework.web.util.UriComponentsBuilder; 19 | /** 20 | * Provides the REST API for the portfolio service. 21 | * 22 | * Provides the following endpoints: 23 | *

    24 | *
  • GET /portfolio/{id} retrieves the portfolio with given account id. 25 | *
  • POST /portfolio{id} adds an order to the portfolio with the given account id. 26 | *

27 | * 28 | * @author David Ferreira Pinto 29 | * 30 | */ 31 | @RestController 32 | public class PortfolioController { 33 | private static final Logger logger = LoggerFactory 34 | .getLogger(PortfolioController.class); 35 | 36 | /** 37 | * the service to delegate to. 38 | */ 39 | @Autowired 40 | private PortfolioService service; 41 | 42 | /** 43 | * Retrieves the portfolio for the given account. 44 | * @param accountId the account to retrieve the portfolio for. 45 | * @return The portfolio with HTTP OK. 46 | */ 47 | @RequestMapping(value = "/portfolio/{id}", method = RequestMethod.GET) 48 | public ResponseEntity getPortfolio(@PathVariable("id") final String accountId) { 49 | logger.debug("PortfolioController: Retrieving portfolio with user id:" + accountId); 50 | Portfolio folio = service.getPortfolio(accountId); 51 | logger.debug("PortfolioController: Retrieved portfolio:" + folio); 52 | return new ResponseEntity(folio, getNoCacheHeaders(), HttpStatus.OK); 53 | } 54 | 55 | private HttpHeaders getNoCacheHeaders() { 56 | HttpHeaders responseHeaders = new HttpHeaders(); 57 | responseHeaders.set("Cache-Control", "no-cache"); 58 | return responseHeaders; 59 | } 60 | /** 61 | * Adds an order to the portfolio of the given account. 62 | * 63 | * @param accountId the account to add the order to. 64 | * @param order The order to add. 65 | * @param builder 66 | * @return The order with HTTP CREATED or BAD REQUEST if it couldn't save. 67 | */ 68 | @RequestMapping(value = "/portfolio/{id}", method = RequestMethod.POST) 69 | public ResponseEntity addOrder(@PathVariable("id") final String accountId, @RequestBody final Order order, UriComponentsBuilder builder) { 70 | logger.debug("Adding Order: " + order); 71 | 72 | //TODO: can do a test to ensure accountId == order.getAccountId(); 73 | 74 | Order savedOrder = service.addOrder(order); 75 | HttpHeaders responseHeaders = new HttpHeaders(); 76 | responseHeaders.setLocation(builder.path("/portfolio/{id}") 77 | .buildAndExpand(accountId).toUri()); 78 | logger.debug("Order added: " + savedOrder); 79 | if (savedOrder != null && savedOrder.getOrderId() != null) { 80 | return new ResponseEntity(savedOrder, responseHeaders, HttpStatus.CREATED); 81 | } else { 82 | logger.warn("Order not saved: " + order); 83 | return new ResponseEntity(savedOrder, responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/domain/Holding.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | /** 8 | * Represents a Holding. 9 | * 10 | * Holding being a set of orders related to a particular stock. 11 | * 12 | * @author David Ferreira Pinto 13 | * 14 | */ 15 | public class Holding { 16 | 17 | private Integer id; 18 | private String symbol; 19 | private Integer quantity = 0; 20 | private BigDecimal purchaseValue = BigDecimal.ZERO; 21 | private BigDecimal sellValue = BigDecimal.ZERO; 22 | private Set orders = new HashSet<>(); 23 | private BigDecimal currentValue = BigDecimal.ZERO; 24 | 25 | public Integer getId() { 26 | return id; 27 | } 28 | 29 | public void setId(Integer id) { 30 | this.id = id; 31 | } 32 | 33 | public String getSymbol() { 34 | return symbol; 35 | } 36 | 37 | public void setSymbol(String symbol) { 38 | this.symbol = symbol; 39 | } 40 | 41 | public Integer getQuantity() { 42 | return quantity; 43 | } 44 | 45 | public void setQuantity(Integer quantity) { 46 | this.quantity = quantity; 47 | } 48 | 49 | public BigDecimal getPurchaseValue() { 50 | return purchaseValue; 51 | } 52 | 53 | public void setPurchaseValue(BigDecimal purchaseValue) { 54 | this.purchaseValue = purchaseValue; 55 | } 56 | 57 | public Set getOrders() { 58 | return orders; 59 | } 60 | 61 | public void setOrders(Set orders) { 62 | this.orders = orders; 63 | } 64 | 65 | public BigDecimal getCurrentValue() { 66 | return currentValue; 67 | } 68 | 69 | public void setCurrentValue(BigDecimal currentValue) { 70 | this.currentValue = currentValue; 71 | } 72 | 73 | public void addOrder(Order order) { 74 | // check order is not already in. 75 | if (orders.contains(order)) { 76 | // TODO: throw RuntimeException?? and do nothing; 77 | } else { 78 | orders.add(order); 79 | // update stats 80 | if (order.getOrderType().equals(OrderType.BUY)) { 81 | setQuantity(getQuantity() + order.getQuantity()); 82 | setPurchaseValue(getPurchaseValue().add(order.getPrice().multiply(new BigDecimal(order.getQuantity())))); 83 | } else if (order.getOrderType().equals(OrderType.SELL)) { 84 | setQuantity(getQuantity() - order.getQuantity()); 85 | setSellValue(getSellValue().add(order.getPrice().multiply(new BigDecimal(order.getQuantity())))); 86 | } 87 | } 88 | } 89 | 90 | public BigDecimal getSellValue() { 91 | return sellValue; 92 | } 93 | 94 | public void setSellValue(BigDecimal sellPrice) { 95 | this.sellValue = sellPrice; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | StringBuilder builder = new StringBuilder(); 101 | builder.append("Holding [id=").append(id).append(", symbol=").append(symbol).append(", quantity=").append(quantity).append(", purchasePrice=").append(purchaseValue).append(", sellPrice=") 102 | .append(sellValue).append(", orders=").append(orders).append(", currentValue=").append(currentValue).append("]"); 103 | return builder.toString(); 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | final int prime = 31; 109 | int result = 1; 110 | result = prime * result + ((currentValue == null) ? 0 : currentValue.hashCode()); 111 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 112 | result = prime * result + ((orders == null) ? 0 : orders.hashCode()); 113 | result = prime * result + ((purchaseValue == null) ? 0 : purchaseValue.hashCode()); 114 | result = prime * result + ((quantity == null) ? 0 : quantity.hashCode()); 115 | result = prime * result + ((sellValue == null) ? 0 : sellValue.hashCode()); 116 | result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); 117 | return result; 118 | } 119 | 120 | @Override 121 | public boolean equals(Object obj) { 122 | if (this == obj) 123 | return true; 124 | if (obj == null) 125 | return false; 126 | if (getClass() != obj.getClass()) 127 | return false; 128 | Holding other = (Holding) obj; 129 | if (currentValue == null) { 130 | if (other.currentValue != null) 131 | return false; 132 | } else if (!currentValue.equals(other.currentValue)) 133 | return false; 134 | if (id == null) { 135 | if (other.id != null) 136 | return false; 137 | } else if (!id.equals(other.id)) 138 | return false; 139 | if (orders == null) { 140 | if (other.orders != null) 141 | return false; 142 | } else if (!orders.equals(other.orders)) 143 | return false; 144 | if (purchaseValue == null) { 145 | if (other.purchaseValue != null) 146 | return false; 147 | } else if (!purchaseValue.equals(other.purchaseValue)) 148 | return false; 149 | if (quantity == null) { 150 | if (other.quantity != null) 151 | return false; 152 | } else if (!quantity.equals(other.quantity)) 153 | return false; 154 | if (sellValue == null) { 155 | if (other.sellValue != null) 156 | return false; 157 | } else if (!sellValue.equals(other.sellValue)) 158 | return false; 159 | if (symbol == null) { 160 | if (other.symbol != null) 161 | return false; 162 | } else if (!symbol.equals(other.symbol)) 163 | return false; 164 | return true; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/domain/OrderType.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.domain; 2 | 3 | /** 4 | * The type of the order. It can be a BUY order or a SELL order. 5 | * 6 | * @author David Ferreira Pinto 7 | * 8 | */ 9 | public enum OrderType { 10 | BUY,SELL 11 | } 12 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/domain/Portfolio.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Represents Portfolio object. 9 | * 10 | * Portfolios are a collection of holdings. 11 | * 12 | * @author David Ferreira Pinto 13 | * 14 | */ 15 | public class Portfolio { 16 | 17 | private String accountId; 18 | private String name; 19 | private BigDecimal currentTotalValue = BigDecimal.ZERO; 20 | private BigDecimal purchaseValue = BigDecimal.ZERO; 21 | private BigDecimal sellValue = BigDecimal.ZERO; 22 | private Map holdings = new HashMap<>(); 23 | 24 | public String getAccountId() { 25 | return accountId; 26 | } 27 | 28 | public void setAccountId(String accountId) { 29 | this.accountId = accountId; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public Map getHoldings() { 41 | return holdings; 42 | } 43 | 44 | public void setHoldings(Map holdings) { 45 | this.holdings = holdings; 46 | } 47 | 48 | public void addHolding(Holding holding) { 49 | holdings.put(holding.getSymbol(), holding); 50 | } 51 | 52 | public Holding getHolding(String symbol) { 53 | return holdings.get(symbol); 54 | } 55 | 56 | public BigDecimal getCurrentTotalValue() { 57 | return currentTotalValue; 58 | } 59 | 60 | public void setCurrentTotalValue(BigDecimal currentTotalValue) { 61 | this.currentTotalValue = currentTotalValue; 62 | } 63 | 64 | /** 65 | * Iterates through each of the holdings aggregating the values. 66 | */ 67 | public void refreshTotalValue() { 68 | this.currentTotalValue = BigDecimal.ZERO; 69 | this.purchaseValue = BigDecimal.ZERO; 70 | this.sellValue = BigDecimal.ZERO; 71 | holdings.values().forEach(holding -> { 72 | this.currentTotalValue = this.currentTotalValue.add(holding.getCurrentValue().multiply(new BigDecimal(holding.getQuantity()))); 73 | this.purchaseValue = this.purchaseValue.add(holding.getPurchaseValue()); 74 | this.sellValue = this.sellValue.add(holding.getSellValue()); 75 | }); 76 | } 77 | 78 | public BigDecimal getSellValue() { 79 | return sellValue; 80 | } 81 | 82 | public void setSellValue(BigDecimal sellValue) { 83 | this.sellValue = sellValue; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | StringBuilder builder = new StringBuilder(); 89 | builder.append("Portfolio [accountId=").append(accountId).append(", name=").append(name).append(", currentTotalValue=").append(currentTotalValue).append(", purchaseValue=") 90 | .append(purchaseValue).append(", sellValue=").append(sellValue).append(", holdings=").append(holdings).append("]"); 91 | return builder.toString(); 92 | } 93 | 94 | public BigDecimal getPurchaseValue() { 95 | return purchaseValue; 96 | } 97 | 98 | public void setPurchaseValue(BigDecimal purchaseValue) { 99 | this.purchaseValue = purchaseValue; 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | final int prime = 31; 105 | int result = 1; 106 | result = prime * result + ((accountId == null) ? 0 : accountId.hashCode()); 107 | result = prime * result + ((currentTotalValue == null) ? 0 : currentTotalValue.hashCode()); 108 | result = prime * result + ((holdings == null) ? 0 : holdings.hashCode()); 109 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 110 | result = prime * result + ((purchaseValue == null) ? 0 : purchaseValue.hashCode()); 111 | result = prime * result + ((sellValue == null) ? 0 : sellValue.hashCode()); 112 | return result; 113 | } 114 | 115 | @Override 116 | public boolean equals(Object obj) { 117 | if (this == obj) 118 | return true; 119 | if (obj == null) 120 | return false; 121 | if (getClass() != obj.getClass()) 122 | return false; 123 | Portfolio other = (Portfolio) obj; 124 | if (accountId == null) { 125 | if (other.accountId != null) 126 | return false; 127 | } else if (!accountId.equals(other.accountId)) 128 | return false; 129 | if (currentTotalValue == null) { 130 | if (other.currentTotalValue != null) 131 | return false; 132 | } else if (!currentTotalValue.equals(other.currentTotalValue)) 133 | return false; 134 | if (holdings == null) { 135 | if (other.holdings != null) 136 | return false; 137 | } else if (!holdings.equals(other.holdings)) 138 | return false; 139 | if (name == null) { 140 | if (other.name != null) 141 | return false; 142 | } else if (!name.equals(other.name)) 143 | return false; 144 | if (purchaseValue == null) { 145 | if (other.purchaseValue != null) 146 | return false; 147 | } else if (!purchaseValue.equals(other.purchaseValue)) 148 | return false; 149 | if (sellValue == null) { 150 | if (other.sellValue != null) 151 | return false; 152 | } else if (!sellValue.equals(other.sellValue)) 153 | return false; 154 | return true; 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.repository; 2 | 3 | 4 | import java.util.List; 5 | 6 | import io.pivotal.portfolio.domain.Order; 7 | 8 | import org.springframework.data.repository.CrudRepository; 9 | /** 10 | * 11 | * @author David Ferreira Pinto 12 | * 13 | */ 14 | public interface OrderRepository extends CrudRepository { 15 | 16 | List findByAccountId(String accountId); 17 | } 18 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/java/io/pivotal/portfolio/service/QuoteRemoteCallService.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.service; 2 | 3 | import java.util.*; 4 | 5 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; 6 | import io.pivotal.portfolio.domain.Quote; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 13 | import org.springframework.cloud.context.config.annotation.RefreshScope; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.web.client.RestTemplate; 16 | 17 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 18 | 19 | /** 20 | * Retrieves quotes from the quote service. Uses hystrix to manage failure. 21 | * 22 | * @author David Ferreira Pinto 23 | * 24 | */ 25 | @Service 26 | @RefreshScope 27 | public class QuoteRemoteCallService { 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(QuoteRemoteCallService.class); 30 | 31 | @Value("${pivotal.quotesService.name}") 32 | private String quotesService; 33 | 34 | @Autowired 35 | @LoadBalanced 36 | private RestTemplate restTemplate; 37 | 38 | /** 39 | * Retrieve multiple quotes. 40 | * 41 | * @param symbols comma separated list of symbols. 42 | * @return 43 | */ 44 | @HystrixCommand(fallbackMethod = "getQuoteFallback", 45 | commandProperties = {@HystrixProperty(name="execution.timeout.enabled", value="false")}) 46 | public List getQuotes(String symbols) { 47 | logger.debug("retrieving multiple quotes: " + symbols); 48 | Quote[] quotesArr = restTemplate.getForObject("http://" + quotesService + "/quotes?q={symbols}", Quote[].class, symbols); 49 | List quotes = Arrays.asList(quotesArr); 50 | logger.debug("Received quotes: {}",quotes); 51 | return quotes; 52 | } 53 | 54 | /** 55 | * Retrieve multiple quotes. 56 | * 57 | * @param symbols 58 | * @return 59 | */ 60 | public List getQuotes(Collection symbols) { 61 | logger.debug("Fetching multiple quotes array: {} ",symbols); 62 | StringBuilder builder = new StringBuilder(); 63 | for (Iterator i = symbols.iterator(); i.hasNext();) { 64 | builder.append(i.next()); 65 | if (i.hasNext()) { 66 | builder.append(","); 67 | } 68 | } 69 | return getQuotes(builder.toString()); 70 | } 71 | 72 | @SuppressWarnings("unused") 73 | private List getQuoteFallback(String symbols) { 74 | List result = new ArrayList<>(); 75 | String[] splitSymbols = symbols.split(","); 76 | 77 | for (String symbol : splitSymbols) { 78 | Quote quote = new Quote(); 79 | quote.setSymbol(symbol); 80 | quote.setStatus("FAILED"); 81 | result.add( quote ); 82 | } 83 | return result; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles.active: local 3 | application: 4 | name: portfolio-service 5 | info: 6 | build: 7 | group: ${group} 8 | name: ${name} 9 | description: ${description} 10 | version: ${version} -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/java/io/pivotal/portfolio/PortfolioApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio; 2 | 3 | 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.SpringApplicationConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringApplicationConfiguration(classes = PortfolioApplication.class) 11 | public class PortfolioApplicationTest { 12 | /** 13 | * test loading of spring context. 14 | */ 15 | @Test 16 | public void contextLoads() { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/java/io/pivotal/portfolio/config/ServiceTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.config; 2 | 3 | import java.math.BigDecimal; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | import io.pivotal.portfolio.domain.Holding; 12 | import io.pivotal.portfolio.domain.Order; 13 | import io.pivotal.portfolio.domain.OrderType; 14 | import io.pivotal.portfolio.domain.Portfolio; 15 | import io.pivotal.portfolio.domain.Quote; 16 | 17 | public class ServiceTestConfiguration { 18 | 19 | public static final String ACCOUNT_ID = "user"; 20 | public static final String SYMBOL = "EMC"; 21 | public static final Integer QUANTITY = 1000; 22 | public static final BigDecimal PRICE = new BigDecimal(10.00); 23 | public static final BigDecimal FEE = new BigDecimal(1.00); 24 | public static final Date COMPLETION_DATE = new Date(1329759342904l); 25 | 26 | public static final String QUOTE_NAME = "EMC Corp"; 27 | public static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss zzzXXX yyyy"); 28 | public static final String QUOTE_DATE_STRING = "Wed May 6 00:00:00 UTC-04:00 2015"; 29 | public static final BigDecimal QUOTE_LAST_PRICE = new BigDecimal(26.135); 30 | public static final BigDecimal QUOTE_CHANGE = new BigDecimal(0.00500000000000256); 31 | public static final Float QUOTE_CHANGE_PERCENT = 0.0191350937619692f; 32 | public static final Float QUOTE_MSDATE = 42130f; 33 | 34 | public static final String COMPANY_EXCHANGE = "NASDAQ"; 35 | 36 | public static List orders() { 37 | List orders = new ArrayList<>(); 38 | orders.add(order()); 39 | return orders; 40 | } 41 | 42 | public static Order order() { 43 | Order order1 = new Order(); 44 | order1.setAccountId(ACCOUNT_ID); 45 | order1.setCompletionDate(COMPLETION_DATE); 46 | order1.setOrderFee(FEE); 47 | order1.setOrderType(OrderType.BUY); 48 | order1.setPrice(PRICE); 49 | order1.setQuantity(QUANTITY); 50 | order1.setSymbol(SYMBOL); 51 | return order1; 52 | } 53 | 54 | public static Order order2() { 55 | Order order1 = new Order(); 56 | order1.setOrderId(1); 57 | order1.setAccountId(ACCOUNT_ID); 58 | order1.setCompletionDate(COMPLETION_DATE); 59 | order1.setOrderFee(FEE); 60 | order1.setOrderType(OrderType.BUY); 61 | order1.setPrice(PRICE); 62 | order1.setQuantity(QUANTITY); 63 | order1.setSymbol(SYMBOL); 64 | return order1; 65 | } 66 | public static Order sellOrder() { 67 | Order order1 = new Order(); 68 | order1.setOrderId(1); 69 | order1.setAccountId(ACCOUNT_ID); 70 | order1.setCompletionDate(COMPLETION_DATE); 71 | order1.setOrderFee(FEE); 72 | order1.setOrderType(OrderType.SELL); 73 | order1.setPrice(PRICE); 74 | order1.setQuantity(QUANTITY); 75 | order1.setSymbol(SYMBOL); 76 | return order1; 77 | } 78 | 79 | public static Quote quote() { 80 | Quote quote = new Quote(); 81 | quote.setName("EMC Corp"); 82 | quote.setSymbol(SYMBOL); 83 | quote.setLastPrice(QUOTE_LAST_PRICE); 84 | quote.setChange(QUOTE_CHANGE); 85 | quote.setChangePercent(QUOTE_CHANGE_PERCENT); 86 | try { 87 | quote.setTimestamp(dateFormat.parse(QUOTE_DATE_STRING)); 88 | } catch (ParseException e) { 89 | // TODO Auto-generated catch block 90 | e.printStackTrace(); 91 | } 92 | quote.setmSDate(QUOTE_MSDATE); 93 | quote.setMarketCap(50755764235.00f); 94 | quote.setVolume(15159291); 95 | quote.setChangeYTD(29.74f); 96 | quote.setChangePercentYTD(-12.1217215870881f); 97 | quote.setHigh(new BigDecimal(0.0)); 98 | quote.setLow(new BigDecimal(0.0)); 99 | quote.setOpen(new BigDecimal(26.52)); 100 | quote.setStatus("SUCCESS"); 101 | return quote; 102 | } 103 | 104 | public static Portfolio portfolio() { 105 | Holding holding = new Holding(); 106 | holding.setId(1); 107 | holding.setQuantity(QUANTITY); 108 | holding.setPurchaseValue(PRICE); 109 | holding.setCurrentValue(QUOTE_LAST_PRICE); 110 | holding.addOrder(order2()); 111 | holding.setSymbol(SYMBOL); 112 | Portfolio folio = new Portfolio(); 113 | folio.setAccountId(ACCOUNT_ID); 114 | folio.addHolding(holding); 115 | folio.refreshTotalValue(); 116 | return folio; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/java/io/pivotal/portfolio/controller/PortfolioControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.controller; 2 | 3 | import static org.mockito.Mockito.when; 4 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 5 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 10 | import io.pivotal.portfolio.config.ServiceTestConfiguration; 11 | import io.pivotal.portfolio.service.PortfolioService; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.InjectMocks; 16 | import org.mockito.Mock; 17 | import org.mockito.MockitoAnnotations; 18 | import org.springframework.http.MediaType; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 21 | 22 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 23 | import com.fasterxml.jackson.databind.ObjectMapper; 24 | 25 | import static org.hamcrest.Matchers.hasSize; 26 | 27 | /** 28 | * Tests for the PortfolioController. 29 | * 30 | * @author David Ferreira Pinto 31 | * 32 | */ 33 | public class PortfolioControllerTest { 34 | MockMvc mockMvc; 35 | 36 | @InjectMocks 37 | PortfolioController controller; 38 | 39 | @Mock 40 | PortfolioService service; 41 | 42 | @Before 43 | public void setup() { 44 | MockitoAnnotations.initMocks(this); 45 | 46 | this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); 47 | } 48 | 49 | @Test 50 | public void getPortfolio() throws Exception { 51 | when(service.getPortfolio(ServiceTestConfiguration.ACCOUNT_ID)) 52 | .thenReturn(ServiceTestConfiguration.portfolio()); 53 | 54 | mockMvc.perform( 55 | get("/portfolio/" + ServiceTestConfiguration.ACCOUNT_ID) 56 | .contentType(MediaType.APPLICATION_JSON)) 57 | .andExpect(status().isOk()) 58 | .andDo(print()) 59 | .andExpect( 60 | content().contentTypeCompatibleWith( 61 | MediaType.APPLICATION_JSON)) 62 | .andExpect( 63 | jsonPath("$.accountId").value( 64 | ServiceTestConfiguration.ACCOUNT_ID)) 65 | .andExpect(jsonPath("$.holdings.*").value(hasSize(1))) 66 | .andDo(print()); 67 | } 68 | 69 | @Test 70 | public void addOrder() throws Exception { 71 | when(service.addOrder(ServiceTestConfiguration.order())) 72 | .thenReturn(ServiceTestConfiguration.order2()); 73 | 74 | mockMvc.perform( 75 | post("/portfolio/" + ServiceTestConfiguration.ACCOUNT_ID) 76 | .contentType(MediaType.APPLICATION_JSON) 77 | .content( 78 | convertObjectToJson(ServiceTestConfiguration.order()))) 79 | .andExpect(status().isCreated()).andDo(print()); 80 | 81 | } 82 | 83 | private byte[] convertObjectToJson(Object request) throws Exception { 84 | ObjectMapper mapper = new ObjectMapper(); 85 | mapper.setSerializationInclusion(Include.NON_NULL); 86 | return mapper.writeValueAsBytes(request); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/java/io/pivotal/portfolio/service/PortfolioServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.service; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | import static org.mockito.Mockito.isA; 6 | import static org.mockito.Mockito.any; 7 | import static org.mockito.Mockito.eq; 8 | 9 | import java.math.BigDecimal; 10 | import java.util.Collections; 11 | 12 | import io.pivotal.portfolio.config.ServiceTestConfiguration; 13 | import io.pivotal.portfolio.domain.Order; 14 | import io.pivotal.portfolio.domain.Portfolio; 15 | import io.pivotal.portfolio.domain.Quote; 16 | import io.pivotal.portfolio.repository.OrderRepository; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.MockitoAnnotations; 23 | import org.springframework.http.HttpStatus; 24 | import org.springframework.http.ResponseEntity; 25 | import org.springframework.test.web.servlet.MockMvc; 26 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 27 | import org.springframework.web.client.RestTemplate; 28 | 29 | public class PortfolioServiceTest { 30 | MockMvc mockMvc; 31 | 32 | @InjectMocks 33 | PortfolioService service; 34 | 35 | @Mock 36 | OrderRepository repo; 37 | 38 | @Mock 39 | RestTemplate restTemplate; 40 | 41 | @Mock 42 | QuoteRemoteCallService quoteService; 43 | 44 | @Before 45 | public void setup() { 46 | MockitoAnnotations.initMocks(this); 47 | 48 | this.mockMvc = MockMvcBuilders.standaloneSetup(service).build(); 49 | } 50 | 51 | @Test 52 | public void doGetPortfolio() { 53 | 54 | when(repo.findByAccountId(ServiceTestConfiguration.ACCOUNT_ID)).thenReturn(ServiceTestConfiguration.orders()); 55 | //when(quoteService.getUri()).thenReturn(uri); 56 | when(quoteService.getQuotes(ServiceTestConfiguration.quote().getSymbol())).thenReturn(Collections.singletonList(ServiceTestConfiguration.quote())); 57 | //when(restTemplate.getForObject("http://" + service.quotesService +"/quote/{symbol}", Quote.class, ServiceTestConfiguration.quote().getSymbol())).thenReturn(ServiceTestConfiguration.quote()); 58 | Portfolio folio = service.getPortfolio(ServiceTestConfiguration.ACCOUNT_ID); 59 | } 60 | @Test 61 | public void doSaveOrder() { 62 | Order returnOrder = ServiceTestConfiguration.order(); 63 | returnOrder.setOrderId(1); 64 | double amount = ServiceTestConfiguration.order().getQuantity()*ServiceTestConfiguration.order().getPrice().doubleValue()+ServiceTestConfiguration.order().getOrderFee().doubleValue(); 65 | ResponseEntity response = new ResponseEntity(100d, HttpStatus.OK); 66 | 67 | 68 | //when(accountService.getUri()).thenReturn(uri); 69 | when(restTemplate.getForEntity("http://" + service.accountsService +"/accounts/{userid}/decreaseBalance/{amount}", Double.class, ServiceTestConfiguration.order().getAccountId(), amount )).thenReturn(response); 70 | when(repo.save(ServiceTestConfiguration.order())).thenReturn(returnOrder); 71 | Order order = service.addOrder(ServiceTestConfiguration.order()); 72 | assertEquals(order, returnOrder); 73 | } 74 | 75 | @Test 76 | public void doSaveOrderNullOrderFee() { 77 | Order returnOrder = ServiceTestConfiguration.order(); 78 | returnOrder.setOrderId(1); 79 | double amount = returnOrder.getQuantity()*returnOrder.getPrice().doubleValue()+returnOrder.getOrderFee().doubleValue(); 80 | ResponseEntity response = new ResponseEntity(100d, HttpStatus.OK); 81 | 82 | 83 | //when(accountService.getUri()).thenReturn(uri); 84 | when(restTemplate.getForEntity(any(), eq(Double.class), any(), any())).thenReturn(response); 85 | when(repo.save(isA(Order.class))).thenReturn(returnOrder); 86 | Order requestOrder = ServiceTestConfiguration.order(); 87 | requestOrder.setOrderFee(null); 88 | Order order = service.addOrder(requestOrder); 89 | assertEquals(order.getOrderFee(), ServiceTestConfiguration.order().getOrderFee()); 90 | } 91 | @Test 92 | public void doSaveOrderSellOrder() { 93 | Order returnOrder = ServiceTestConfiguration.sellOrder(); 94 | returnOrder.setOrderId(1); 95 | double amount = ServiceTestConfiguration.sellOrder().getQuantity()*ServiceTestConfiguration.sellOrder().getPrice().doubleValue()-ServiceTestConfiguration.sellOrder().getOrderFee().doubleValue(); 96 | ResponseEntity response = new ResponseEntity(100d, HttpStatus.OK); 97 | 98 | 99 | //when(accountService.getUri()).thenReturn(uri); 100 | when(restTemplate.getForEntity("http://" + service.accountsService +"/accounts/{userid}/increaseBalance/{amount}", Double.class, ServiceTestConfiguration.sellOrder().getAccountId(), amount )).thenReturn(response); 101 | when(repo.save(ServiceTestConfiguration.sellOrder())).thenReturn(returnOrder); 102 | Order order = service.addOrder(ServiceTestConfiguration.sellOrder()); 103 | assertEquals(order, returnOrder); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/java/io/pivotal/portfolio/service/QuoteRemoteCallServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.portfolio.service; 2 | 3 | import io.pivotal.portfolio.PortfolioApplication; 4 | import io.pivotal.portfolio.config.ServiceTestConfiguration; 5 | import io.pivotal.portfolio.domain.Quote; 6 | 7 | import org.junit.Before; 8 | import org.junit.Ignore; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.mockito.MockitoAnnotations; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertNotEquals; 17 | import static org.mockito.Mockito.when; 18 | 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.beans.factory.annotation.Value; 21 | import org.springframework.boot.test.SpringApplicationConfiguration; 22 | 23 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 24 | import org.springframework.web.client.RestTemplate; 25 | 26 | import java.util.Collections; 27 | import java.util.List; 28 | 29 | 30 | @RunWith(SpringJUnit4ClassRunner.class) 31 | @SpringApplicationConfiguration(classes = PortfolioApplication.class) 32 | public class QuoteRemoteCallServiceTest { 33 | 34 | @Value("${pivotal.quotesService.name}") 35 | private String quotesURI; 36 | 37 | @Autowired 38 | @InjectMocks 39 | QuoteRemoteCallService service; 40 | 41 | @Mock 42 | RestTemplate restTemplate; 43 | 44 | @Before 45 | public void setup() { 46 | MockitoAnnotations.initMocks(this); 47 | } 48 | 49 | /* 50 | * resttemplate not being injected into service thus cannot test success of hystrix 51 | */ 52 | @Test 53 | @Ignore 54 | public void doGetQuote() { 55 | when(restTemplate.getForObject("http://" + quotesURI + "/quotes?q={symbol}", Quote[].class, ServiceTestConfiguration.SYMBOL)). 56 | thenReturn( new Quote[]{ServiceTestConfiguration.quote()}); 57 | 58 | List quote = service.getQuotes(ServiceTestConfiguration.SYMBOL); 59 | assertEquals(ServiceTestConfiguration.quote(),quote.get(0)); 60 | } 61 | @Test 62 | public void doGetQuoteFailure() { 63 | when(restTemplate.getForObject("http://" + quotesURI + "/quotes?q={symbol}", Quote[].class, ServiceTestConfiguration.SYMBOL)). 64 | thenThrow(new RuntimeException("Deliberately throwing an exception 1")); 65 | 66 | List quote = service.getQuotes(ServiceTestConfiguration.SYMBOL); 67 | assertNotEquals(ServiceTestConfiguration.quote(),quote.get(0)); 68 | Quote emptyQuote = new Quote(); 69 | emptyQuote.setSymbol(ServiceTestConfiguration.SYMBOL); 70 | emptyQuote.setStatus("FAILED"); 71 | assertEquals(Collections.singletonList(emptyQuote),quote); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /springboottrades-portfolio/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | pivotal.quotesService.name=quotes-service -------------------------------------------------------------------------------- /springboottrades-quotes/manifest-unversioned.yml: -------------------------------------------------------------------------------- 1 | --- 2 | timeout: 180 3 | instances: 1 4 | memory: 512M 5 | env: 6 | SPRING_PROFILES_ACTIVE: cloud 7 | JAVA_OPTS: -Djava.security.egd=file:///dev/urandom 8 | JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: { memory_sizes: { metaspace: 100m }}]' 9 | applications: 10 | - name: quotes 11 | random-route: true 12 | path: libs/quotes-${version}.jar 13 | services: [ discovery-service, circuit-breaker-dashboard, config-server ] 14 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/QuotesApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.sleuth.Sampler; 8 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler; 9 | import org.springframework.context.annotation.Bean; 10 | /** 11 | * Microservice to fetch current quotes. 12 | * 13 | * Spring Boot application to provide a service to retrieve current Quote information. 14 | * The application registers with a registry service - Eureka. 15 | * 16 | * @author David Ferreira Pinto 17 | * 18 | */ 19 | @SpringBootApplication 20 | @EnableDiscoveryClient 21 | @EnableCircuitBreaker 22 | public class QuotesApplication { 23 | 24 | @Bean 25 | public Sampler defaultSampler() { 26 | return new AlwaysSampler(); 27 | } 28 | 29 | public static void main(String[] args) { 30 | SpringApplication.run(QuotesApplication.class, args); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/controller/QuoteController.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import io.pivotal.quotes.domain.CompanyInfo; 10 | import io.pivotal.quotes.domain.Quote; 11 | import io.pivotal.quotes.exception.SymbolNotFoundException; 12 | import io.pivotal.quotes.service.QuoteService; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.http.HttpHeaders; 18 | import org.springframework.http.HttpStatus; 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.web.bind.annotation.*; 21 | 22 | /** 23 | * Rest Controller providing the REST API for the Quote Service. Provides two calls (both HTTP GET methods): 24 | * 25 | * - /quotes?q={symbol} - Retrieves the current 26 | * quotes for a comma-separated symbol list. 27 | * - /company/{name} - Retrieves a list of company 28 | * information for companies that match the {name}. 29 | * 30 | * @author David Ferreira Pinto 31 | */ 32 | @RestController 33 | public class QuoteController { 34 | private static final Logger logger = LoggerFactory.getLogger(QuoteController.class); 35 | 36 | /** 37 | * The service to delegate calls to. 38 | */ 39 | @Autowired 40 | private QuoteService service; 41 | 42 | /** 43 | * Retrieves the current quotes for the given symbols. 44 | * 45 | * @param query 46 | * request parameter with q=symbol,symbol 47 | * @return The Quote 48 | * @throws SymbolNotFoundException 49 | * if the symbol is not valid. 50 | */ 51 | @RequestMapping(value = "/quotes", method = RequestMethod.GET) 52 | public ResponseEntity> getQuotes(@RequestParam(value="q", required=false) String query) throws SymbolNotFoundException { 53 | logger.debug("received Quote query for: %s", query); 54 | if (query == null) { 55 | //return empty list. 56 | return new ResponseEntity<>(new ArrayList<>(), getNoCacheHeaders(), HttpStatus.OK); 57 | } 58 | 59 | List quotes = service.getQuotes( query ); 60 | 61 | logger.info(String.format("Retrieved symbols: %s with quotes {}", query)); 62 | return new ResponseEntity<>(quotes, getNoCacheHeaders(), HttpStatus.OK); 63 | } 64 | 65 | /** 66 | * Searches for companies that have a name or symbol matching the parameter. 67 | * 68 | * @param name 69 | * The name or symbol to search for. 70 | * @return The list of companies that match the search parameter. 71 | */ 72 | @RequestMapping(value = "/company/{name}", method = RequestMethod.GET) 73 | public ResponseEntity> getCompanies(@PathVariable("name") final String name) { 74 | logger.debug("QuoteController.getCompanies: retrieving companies for: " + name); 75 | List companies = service.getCompanyInfo(name); 76 | logger.info(String.format("Retrieved companies with search parameter: %s - list: {}", name), companies); 77 | return new ResponseEntity<>(companies, HttpStatus.OK); 78 | } 79 | 80 | /** 81 | * Generates HttpHeaders that have the no-cache set. 82 | * 83 | * @return HttpHeaders. 84 | */ 85 | private HttpHeaders getNoCacheHeaders() { 86 | HttpHeaders responseHeaders = new HttpHeaders(); 87 | responseHeaders.set("Cache-Control", "no-cache"); 88 | return responseHeaders; 89 | } 90 | 91 | /** 92 | * Handles the response to the client if there is any exception during the 93 | * processing of HTTP requests. 94 | * 95 | * @param e 96 | * The exception thrown during the processing of the request. 97 | * @param response 98 | * The HttpResponse object. 99 | * @throws IOException 100 | */ 101 | @ExceptionHandler({ Exception.class }) 102 | public void handleException(Exception e, HttpServletResponse response) throws IOException { 103 | logger.warn("Handle Error: " + e.getMessage()); 104 | logger.warn("Exception:", e); 105 | response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), "ERROR: " + e.getMessage()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/domain/CompanyInfo.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Represents company information. 7 | * 8 | * { 9 | * "Symbol":"NFLX", 10 | * "Name":"Netflix Inc", 11 | * "Exchange":"NASDAQ" 12 | * } 13 | * 14 | * @author David Ferreira Pinto 15 | * 16 | */ 17 | public class CompanyInfo implements Comparable { 18 | 19 | @JsonProperty("Symbol") 20 | private String symbol; 21 | @JsonProperty("Name") 22 | private String name; 23 | @JsonProperty("Exchange") 24 | private String exchange; 25 | 26 | public String getSymbol() { 27 | return symbol; 28 | } 29 | public void setSymbol(String symbol) { 30 | this.symbol = symbol; 31 | } 32 | public String getName() { 33 | return name; 34 | } 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | public String getExchange() { 39 | return exchange; 40 | } 41 | public void setExchange(String exchange) { 42 | this.exchange = exchange; 43 | } 44 | @Override 45 | public String toString() { 46 | StringBuilder builder = new StringBuilder(); 47 | builder.append("CompanyInfo [symbol=").append(symbol).append(", name=") 48 | .append(name).append(", exchange=").append(exchange) 49 | .append("]"); 50 | return builder.toString(); 51 | } 52 | @Override 53 | public int compareTo(CompanyInfo o) { 54 | if(o == null){ 55 | return -1; 56 | } 57 | return this.getSymbol().compareTo(o.getSymbol()); 58 | } 59 | @Override 60 | public int hashCode() { 61 | final int prime = 31; 62 | int result = 1; 63 | result = prime * result + ((exchange == null) ? 0 : exchange.hashCode()); 64 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 65 | result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); 66 | return result; 67 | } 68 | @Override 69 | public boolean equals(Object obj) { 70 | if (this == obj) 71 | return true; 72 | if (obj == null) 73 | return false; 74 | if (getClass() != obj.getClass()) 75 | return false; 76 | CompanyInfo other = (CompanyInfo) obj; 77 | if (exchange == null) { 78 | if (other.exchange != null) 79 | return false; 80 | } else if (!exchange.equals(other.exchange)) 81 | return false; 82 | if (name == null) { 83 | if (other.name != null) 84 | return false; 85 | } else if (!name.equals(other.name)) 86 | return false; 87 | if (symbol == null) { 88 | if (other.symbol != null) 89 | return false; 90 | } else if (!symbol.equals(other.symbol)) 91 | return false; 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/domain/QuoteMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.pivotal.quotes.domain; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | 9 | /** 10 | * Singleton Class to map between quote objects as returned by different public quotes URLS 11 | * 12 | * @author Sufyaan Kazi 13 | */ 14 | public class QuoteMapper { 15 | public static QuoteMapper INSTANCE = new QuoteMapper(); 16 | 17 | private QuoteMapper(){ 18 | super(); 19 | } 20 | 21 | /** 22 | * Maps between a Yahoo Quote to Markit Quote 23 | * 24 | * @param yQuote YahooQuote 25 | * @return new quote 26 | * @author David Ferreira Pinto 27 | */ 28 | public Quote mapFromYahooQuote(YahooQuote yQuote, Date created){ 29 | if(yQuote == null){ 30 | return null; 31 | } 32 | 33 | 34 | Quote mappedQuote = new Quote(); 35 | mappedQuote.setChange(yQuote.getChange()); 36 | if (yQuote.getPercentChange() != null) { 37 | mappedQuote.setChangePercent(Float.parseFloat(yQuote.getPercentChange().replace("%", ""))); 38 | } 39 | mappedQuote.setChangePercentYTD(null); 40 | mappedQuote.setChangeYTD(null); 41 | mappedQuote.setHigh(yQuote.getDaysHigh()); 42 | mappedQuote.setLastPrice(yQuote.getLastTradePriceOnly()); 43 | mappedQuote.setLow(yQuote.getDaysLow()); 44 | mappedQuote.setMarketCap(null); 45 | mappedQuote.setmSDate(null); 46 | mappedQuote.setName(yQuote.getName()); 47 | mappedQuote.setOpen(yQuote.getOpen()); 48 | mappedQuote.setStatus("SUCCESS"); 49 | mappedQuote.setSymbol(yQuote.getSymbol()); 50 | mappedQuote.setTimestamp(created); 51 | mappedQuote.setVolume(yQuote.getVolume()); 52 | 53 | return mappedQuote; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/domain/YahooQuoteList.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.domain; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | public class YahooQuoteList { 8 | 9 | @JsonProperty(value="quote") 10 | private List quote; 11 | 12 | public List getQuote() { 13 | return quote; 14 | } 15 | 16 | public void setQuote(List quote) { 17 | this.quote = quote; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | StringBuilder builder = new StringBuilder(); 23 | builder.append("YahooQuoteList [quote=").append(quote).append("]"); 24 | return builder.toString(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/domain/YahooQuoteResponses.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class YahooQuoteResponses { 8 | 9 | @JsonProperty("query") 10 | private YahooResults results; 11 | 12 | @Override 13 | public String toString() { 14 | StringBuilder builder = new StringBuilder(); 15 | builder.append("YahooQuoteResponses [results=").append(results) 16 | .append("]"); 17 | return builder.toString(); 18 | } 19 | 20 | public YahooResults getResults() { 21 | return results; 22 | } 23 | 24 | public void setResults(YahooResults results) { 25 | this.results = results; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/domain/YahooResults.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.domain; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public class YahooResults { 11 | 12 | @JsonProperty("results") 13 | private YahooQuoteList quoteList; 14 | 15 | @JsonProperty("count") 16 | private Integer count; 17 | 18 | @JsonProperty("created") 19 | @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss'Z'", locale="ENGLISH") //"2015-11-03T11:57:31Z" 20 | private Date created; 21 | 22 | @JsonProperty("lang") 23 | private String lang; 24 | 25 | public YahooQuoteList getQuoteList() { 26 | return quoteList; 27 | } 28 | 29 | public void setQuoteList(YahooQuoteList quoteList) { 30 | this.quoteList = quoteList; 31 | } 32 | 33 | public Integer getCount() { 34 | return count; 35 | } 36 | 37 | public void setCount(Integer count) { 38 | this.count = count; 39 | } 40 | 41 | public Date getCreated() { 42 | return created; 43 | } 44 | 45 | public void setCreated(Date created) { 46 | this.created = created; 47 | } 48 | 49 | public String getLang() { 50 | return lang; 51 | } 52 | 53 | public void setLang(String lang) { 54 | this.lang = lang; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | StringBuilder builder = new StringBuilder(); 60 | builder.append("YahooResults [quoteList=").append(quoteList) 61 | .append(", count=").append(count).append(", created=") 62 | .append(created).append(", lang=").append(lang).append("]"); 63 | return builder.toString(); 64 | } 65 | } -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/java/io/pivotal/quotes/exception/SymbolNotFoundException.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.exception; 2 | 3 | /** 4 | * Exception representing that a quote symbol cannot be found. 5 | * @author David Ferreira Pinto 6 | * 7 | */ 8 | public class SymbolNotFoundException extends Exception { 9 | 10 | public SymbolNotFoundException(String message ) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles.active: local 3 | application: 4 | name: quotes-service 5 | info: 6 | build: 7 | group: ${group} 8 | name: ${name} 9 | description: ${description} 10 | version: ${version} -------------------------------------------------------------------------------- /springboottrades-quotes/src/test/java/io/pivotal/quotes/QuotesApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.SpringApplicationConfiguration; 6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 7 | /** 8 | * Tests for the Quotes Application. 9 | * @author David Ferreira Pinto 10 | * 11 | */ 12 | @RunWith(SpringJUnit4ClassRunner.class) 13 | @SpringApplicationConfiguration(classes = QuotesApplication.class) 14 | public class QuotesApplicationTest { 15 | /** 16 | * test loading of spring context. 17 | */ 18 | @Test 19 | public void contextLoads() { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/test/java/io/pivotal/quotes/configuration/TestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.configuration; 2 | 3 | import java.math.BigDecimal; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | import java.util.Locale; 10 | 11 | import io.pivotal.quotes.domain.CompanyInfo; 12 | import io.pivotal.quotes.domain.Quote; 13 | /** 14 | * Defaults to use for the tests. 15 | * 16 | * @author David Ferreira Pinto 17 | * 18 | */ 19 | public class TestConfiguration { 20 | 21 | public static final String QUOTE_SYMBOL = "EMC"; 22 | public static final String QUOTE_NAME = "EMC Corporation Common Stock"; 23 | public static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss zzzXXX yyyy", Locale.ENGLISH); 24 | public static final String QUOTE_DATE_STRING = "Wed May 6 00:00:00 UTC-04:00 2015"; 25 | public static final BigDecimal QUOTE_LAST_PRICE = new BigDecimal(26.135); 26 | public static final BigDecimal QUOTE_CHANGE = new BigDecimal(0.00500000000000256d); 27 | public static final Float QUOTE_CHANGE_PERCENT = 0.0191350937619692f; 28 | public static final Float QUOTE_MSDATE = 42130f; 29 | 30 | public static final String COMPANY_EXCHANGE = "NASDAQ"; 31 | public static final String NULL_QUOTE_SYMBOL = "LALALALA"; 32 | 33 | public static final String QUOTE_SYMBOLS = "EMC,IBM"; 34 | /* 35 | * {"Status":"SUCCESS","Name":"EMC Corp","Symbol":"EMC","LastPrice":26.135, 36 | * "Change":0.00500000000000256,"ChangePercent":0.0191350937619692, 37 | * "Timestamp":"Wed May 6 00:00:00 UTC-04:00 2015","MSDate":42130, 38 | * "MarketCap":50755764235,"Volume":15159291,"ChangeYTD":29.74, 39 | * "ChangePercentYTD":-12.1217215870881,"High":0,"Low":0,"Open":26.52} 40 | */ 41 | public static Quote quote() { 42 | Quote quote = new Quote(); 43 | quote.setName(QUOTE_NAME); 44 | quote.setSymbol(QUOTE_SYMBOL); 45 | quote.setLastPrice(QUOTE_LAST_PRICE); 46 | quote.setChange(QUOTE_CHANGE); 47 | quote.setChangePercent(QUOTE_CHANGE_PERCENT); 48 | try { 49 | quote.setTimestamp(dateFormat.parse(QUOTE_DATE_STRING)); 50 | } catch (ParseException e) { 51 | // TODO Auto-generated catch block 52 | e.printStackTrace(); 53 | } 54 | quote.setmSDate(QUOTE_MSDATE); 55 | quote.setMarketCap(50755764235.00f); 56 | quote.setVolume(15159291); 57 | quote.setChangeYTD(29.74f); 58 | quote.setChangePercentYTD(-12.1217215870881f); 59 | quote.setHigh(new BigDecimal(0.0)); 60 | quote.setLow(new BigDecimal(0.0)); 61 | quote.setOpen(new BigDecimal(26.52)); 62 | return quote; 63 | 64 | } 65 | public static Quote quote2() { 66 | Quote quote = new Quote(); 67 | quote.setName("International Business Machine"); 68 | quote.setSymbol("IBM"); 69 | quote.setLastPrice(QUOTE_LAST_PRICE); 70 | quote.setChange(QUOTE_CHANGE); 71 | quote.setChangePercent(QUOTE_CHANGE_PERCENT); 72 | try { 73 | quote.setTimestamp(dateFormat.parse(QUOTE_DATE_STRING)); 74 | } catch (ParseException e) { 75 | // TODO Auto-generated catch block 76 | e.printStackTrace(); 77 | } 78 | quote.setmSDate(QUOTE_MSDATE); 79 | quote.setMarketCap(50755764235.00f); 80 | quote.setVolume(15159291); 81 | quote.setChangeYTD(29.74f); 82 | quote.setChangePercentYTD(-12.1217215870881f); 83 | quote.setHigh(new BigDecimal(0.0)); 84 | quote.setLow(new BigDecimal(0.0)); 85 | quote.setOpen(new BigDecimal(26.52)); 86 | return quote; 87 | 88 | } 89 | public static List quotes() { 90 | List quotes = new ArrayList<>(); 91 | quotes.add(quote()); 92 | quotes.add(quote2()); 93 | return quotes; 94 | } 95 | 96 | public static CompanyInfo company() { 97 | CompanyInfo comp = new CompanyInfo(); 98 | comp.setExchange(COMPANY_EXCHANGE); 99 | comp.setName(QUOTE_NAME); 100 | comp.setSymbol(QUOTE_SYMBOL); 101 | return comp; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/test/java/io/pivotal/quotes/service/QuoteServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.quotes.service; 2 | 3 | import io.pivotal.quotes.QuotesApplication; 4 | import io.pivotal.quotes.configuration.TestConfiguration; 5 | import io.pivotal.quotes.domain.CompanyInfo; 6 | import io.pivotal.quotes.domain.Quote; 7 | import io.pivotal.quotes.exception.SymbolNotFoundException; 8 | import org.junit.Before; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.ExpectedException; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | import org.mockito.MockitoAnnotations; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.SpringApplicationConfiguration; 18 | import org.springframework.test.context.ActiveProfiles; 19 | import org.springframework.test.context.TestPropertySource; 20 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 21 | import org.springframework.test.web.servlet.MockMvc; 22 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 23 | import org.springframework.web.client.RestTemplate; 24 | 25 | import java.util.List; 26 | 27 | import static org.hamcrest.Matchers.isA; 28 | import static org.junit.Assert.*; 29 | 30 | /** 31 | * Tests the QuoteService. 32 | * @author David Ferreira Pinto 33 | * 34 | */ 35 | 36 | @RunWith(SpringJUnit4ClassRunner.class) 37 | @SpringApplicationConfiguration(classes = QuotesApplication.class) 38 | @TestPropertySource(locations="classpath:application-test.yml") 39 | @ActiveProfiles("test") 40 | public class QuoteServiceTest { 41 | MockMvc mockMvc; 42 | 43 | @InjectMocks 44 | @Autowired 45 | QuoteService service; 46 | 47 | @Mock 48 | RestTemplate restTemplate; 49 | 50 | @Before 51 | public void setup() { 52 | MockitoAnnotations.initMocks(this); 53 | 54 | this.mockMvc = MockMvcBuilders.standaloneSetup(service).build(); 55 | 56 | } 57 | /** 58 | * Tests retrieving a quote from the external service. 59 | * @throws Exception 60 | */ 61 | @Test 62 | public void getQuote() throws Exception { 63 | Quote quote = service.getQuotes(TestConfiguration.QUOTE_SYMBOL).get(0); 64 | assertEquals(TestConfiguration.QUOTE_SYMBOL, quote.getSymbol()); 65 | assertEquals(TestConfiguration.QUOTE_NAME, quote.getName()); 66 | } 67 | /** 68 | * Tests retrieving a quote with an unknown/null symbol from the external service. 69 | * @throws Exception 70 | */ 71 | @Rule 72 | public ExpectedException thrown = ExpectedException.none(); 73 | 74 | @Test 75 | public void getNullQuote() throws Exception{ 76 | thrown.expect(com.netflix.hystrix.exception.HystrixRuntimeException.class); 77 | thrown.expectCause(isA(SymbolNotFoundException.class)); 78 | service.getQuotes(TestConfiguration.NULL_QUOTE_SYMBOL); 79 | } 80 | 81 | /** 82 | * tests retrieving company information from external service. 83 | * @throws Exception 84 | */ 85 | @Test 86 | public void getCompanyInfo() throws Exception { 87 | List comps = service.getCompanyInfo(TestConfiguration.QUOTE_SYMBOL); 88 | assertFalse(comps.isEmpty()); 89 | boolean pass = false; 90 | for (CompanyInfo info : comps) { 91 | if (info.getSymbol().equals(TestConfiguration.QUOTE_SYMBOL)) { 92 | pass = true; 93 | } 94 | } 95 | assertTrue(pass); 96 | } 97 | /** 98 | * tests retrieving company information from external service with unkown company. 99 | * @throws Exception 100 | */ 101 | @Test 102 | public void getNullCompanyInfo() throws Exception { 103 | List comps = service.getCompanyInfo(TestConfiguration.NULL_QUOTE_SYMBOL); 104 | assertTrue(comps.isEmpty()); 105 | } 106 | 107 | /** 108 | * test yahoo service with multiple quotes 109 | * @throws Exception 110 | */ 111 | @Test 112 | public void getQuotes() throws Exception { 113 | List quotes = service.getQuotes(TestConfiguration.QUOTE_SYMBOLS); 114 | assertNotNull("should have 2 quotes",quotes); 115 | assertEquals("should have 2 quotes",quotes.size(), 2); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /springboottrades-quotes/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | pivotal: 2 | quotes: 3 | quotes_url: http://dev.markitondemand.com/Api/v2/Quote/json?symbol={symbol} 4 | quotes_url2: http://globalquotes.xignite.com/v3/xGlobalQuotes.json/GetGlobalDelayedQuote?IdentifierType=Symbol&Identifier={symbol}&_token=5C00193922334CF2BE564F76D2E233CF 5 | companies_url: http://dev.markitondemand.com/Api/v2/Lookup/json?input={name} 6 | yahoo_rest_query: https://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.quotes where symbol in ('{symbol}')&format={fmt}&env={env} 7 | yahoo_env: http://datatables.org/alltables.env 8 | -------------------------------------------------------------------------------- /springboottrades-web/manifest-unversioned.yml: -------------------------------------------------------------------------------- 1 | --- 2 | timeout: 180 3 | instances: 1 4 | memory: 512M 5 | env: 6 | SPRING_PROFILES_ACTIVE: cloud 7 | JAVA_OPTS: -Djava.security.egd=file:///dev/urandom 8 | JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: { memory_sizes: { metaspace: 100m }}]' 9 | applications: 10 | - name: webtrader 11 | random-route: true 12 | path: libs/web-${version}.jar 13 | services: [ discovery-service, circuit-breaker-dashboard, config-server ] 14 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/WebApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 7 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 8 | import org.springframework.cloud.sleuth.Sampler; 9 | import org.springframework.cloud.sleuth.sampler.AlwaysSampler; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.scheduling.annotation.EnableScheduling; 13 | 14 | @SpringBootApplication 15 | @EnableDiscoveryClient 16 | @EnableCircuitBreaker 17 | @EnableScheduling 18 | public class WebApplication { 19 | 20 | @Bean 21 | public Sampler defaultSampler() { 22 | return new AlwaysSampler(); 23 | } 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(WebApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 6 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 8 | 9 | @Configuration 10 | @Import({ThymeleafConfig.class}) 11 | public class MvcConfig extends WebMvcConfigurerAdapter { 12 | @Override 13 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 14 | if (!registry.hasMappingForPattern("/webjars/**")) { 15 | registry.addResourceHandler("/webjars/**").addResourceLocations( 16 | "classpath:/META-INF/resources/webjars/"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/config/ThymeleafConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.config; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import nz.net.ultraq.thymeleaf.LayoutDialect; 7 | 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.thymeleaf.dialect.IDialect; 11 | import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect; 12 | import org.thymeleaf.spring4.SpringTemplateEngine; 13 | import org.thymeleaf.spring4.view.ThymeleafViewResolver; 14 | import org.thymeleaf.templateresolver.ServletContextTemplateResolver; 15 | 16 | @Configuration 17 | public class ThymeleafConfig { 18 | 19 | /* 20 | * Spring Boot Autoconfig adding the SpringSecurityDialect bean automatically from 21 | * 1.3.0.M4???? 22 | */ 23 | /* 24 | @Bean 25 | public SpringSecurityDialect springSecurityDialect() { 26 | return new SpringSecurityDialect(); 27 | } 28 | */ 29 | 30 | /* 31 | @Bean 32 | public ServletContextTemplateResolver templateResolver() { 33 | ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(); 34 | resolver.setPrefix("templates/"); 35 | resolver.setSuffix(".html"); 36 | resolver.setTemplateMode("HTML5"); 37 | resolver.setOrder(1); 38 | return resolver; 39 | } 40 | 41 | @Bean 42 | public SpringTemplateEngine templateEngine() { 43 | SpringTemplateEngine engine = new SpringTemplateEngine(); 44 | engine.setTemplateResolver(templateResolver()); 45 | Set dialects = new HashSet(); 46 | dialects.add(new SpringSecurityDialect()); 47 | dialects.add(new LayoutDialect()); 48 | engine.setAdditionalDialects(dialects); 49 | return engine; 50 | } 51 | 52 | @Bean 53 | public ThymeleafViewResolver thymeleafViewResolver() { 54 | ThymeleafViewResolver resolver = new ThymeleafViewResolver(); 55 | resolver.setTemplateEngine(templateEngine()); 56 | return resolver; 57 | } 58 | */ 59 | } 60 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/config/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.config; 2 | 3 | import io.pivotal.web.security.CustomAuthenticationProvider; 4 | import io.pivotal.web.security.CustomCredentialsService; 5 | import io.pivotal.web.security.LogoutSuccessHandler; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; 14 | 15 | @Configuration 16 | @EnableWebMvcSecurity 17 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 18 | 19 | @Autowired 20 | private CustomCredentialsService customCredentialsService; 21 | 22 | @Autowired 23 | private CustomAuthenticationProvider customAuthenticationProvider; 24 | 25 | @Autowired 26 | private LogoutSuccessHandler logoutSuccessHandler; 27 | 28 | @Override 29 | protected void configure(HttpSecurity http) throws Exception { 30 | http 31 | .authorizeRequests() 32 | .antMatchers("/", "/registration","/hystrix.stream").permitAll() 33 | .anyRequest().authenticated() 34 | .and() 35 | .formLogin() 36 | .loginPage("/login") 37 | .loginProcessingUrl("/login") 38 | .permitAll() 39 | .and() 40 | .logout() 41 | .logoutSuccessHandler(logoutSuccessHandler) 42 | .permitAll(); 43 | } 44 | @Override 45 | public void configure(WebSecurity web) throws Exception { 46 | web 47 | .ignoring() 48 | .antMatchers("/webjars/**") 49 | .antMatchers("/images/**").antMatchers("/css/**").antMatchers("/js/**"); 50 | } 51 | 52 | @Autowired 53 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 54 | auth.authenticationProvider(customAuthenticationProvider); 55 | auth.userDetailsService(customCredentialsService); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/controller/PortfolioController.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.controller; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import io.pivotal.web.domain.Order; 6 | import io.pivotal.web.domain.Search; 7 | import io.pivotal.web.service.MarketService; 8 | import io.pivotal.web.service.MarketSummaryService; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.ui.Model; 18 | import org.springframework.web.bind.annotation.ExceptionHandler; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestMethod; 21 | import org.springframework.web.client.HttpServerErrorException; 22 | import org.springframework.web.servlet.ModelAndView; 23 | 24 | @Controller 25 | public class PortfolioController { 26 | private static final Logger logger = LoggerFactory 27 | .getLogger(PortfolioController.class); 28 | 29 | @Autowired 30 | private MarketService marketService; 31 | 32 | @Autowired 33 | private MarketSummaryService summaryService; 34 | 35 | @RequestMapping(value = "/portfolio", method = RequestMethod.GET) 36 | public String portfolio(Model model) { 37 | logger.debug("/portfolio"); 38 | model.addAttribute("marketSummary", summaryService.getMarketSummary()); 39 | 40 | //check if user is logged in! 41 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 42 | if (!(authentication instanceof AnonymousAuthenticationToken)) { 43 | String currentUserName = authentication.getName(); 44 | logger.debug("portfolio: User logged in: " + currentUserName); 45 | 46 | //TODO: add account summary. 47 | try { 48 | model.addAttribute("portfolio",marketService.getPortfolio(currentUserName)); 49 | } catch (HttpServerErrorException e) { 50 | logger.debug("error retrieving portfolfio: " + e.getMessage()); 51 | model.addAttribute("portfolioRetrievalError",e.getMessage()); 52 | } 53 | model.addAttribute("order", new Order()); 54 | } 55 | 56 | return "portfolio"; 57 | } 58 | 59 | @ExceptionHandler({ Exception.class }) 60 | public ModelAndView error(HttpServletRequest req, Exception exception) { 61 | logger.debug("Handling error: " + exception); 62 | logger.warn("Exception: ", exception); 63 | ModelAndView model = new ModelAndView(); 64 | model.addObject("errorCode", exception.getMessage()); 65 | model.addObject("errorMessage", exception); 66 | model.setViewName("error"); 67 | return model; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.controller; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | import java.util.Map; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | import io.pivotal.web.domain.Account; 10 | import io.pivotal.web.domain.AuthenticationRequest; 11 | import io.pivotal.web.service.MarketSummaryService; 12 | import io.pivotal.web.service.UserService; 13 | import io.pivotal.web.service.MarketService; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.context.annotation.ComponentScan; 19 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 20 | import org.springframework.security.core.Authentication; 21 | import org.springframework.security.core.context.SecurityContextHolder; 22 | import org.springframework.stereotype.Controller; 23 | import org.springframework.ui.Model; 24 | import org.springframework.web.bind.annotation.ExceptionHandler; 25 | import org.springframework.web.bind.annotation.ModelAttribute; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestMethod; 28 | import org.springframework.web.client.HttpServerErrorException; 29 | import org.springframework.web.servlet.ModelAndView; 30 | 31 | @Controller 32 | public class UserController { 33 | private static final Logger logger = LoggerFactory 34 | .getLogger(UserController.class); 35 | 36 | @Autowired 37 | private UserService accountService; 38 | 39 | @Autowired 40 | private MarketService marketService; 41 | 42 | @Autowired 43 | private MarketSummaryService summaryService; 44 | 45 | @RequestMapping(value = "/", method = RequestMethod.GET) 46 | public String showHome(Model model) { 47 | if (!model.containsAttribute("login")) { 48 | model.addAttribute("login", new AuthenticationRequest()); 49 | } 50 | model.addAttribute("marketSummary", summaryService.getMarketSummary()); 51 | 52 | //check if user is logged in! 53 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 54 | if (!(authentication instanceof AnonymousAuthenticationToken)) { 55 | String currentUserName = authentication.getName(); 56 | logger.debug("User logged in: " + currentUserName); 57 | 58 | try { 59 | model.addAttribute("portfolio",marketService.getPortfolio(currentUserName)); 60 | } catch (HttpServerErrorException e) { 61 | model.addAttribute("portfolioRetrievalError",e.getMessage()); 62 | } 63 | model.addAttribute("account",accountService.getAccount(currentUserName)); 64 | } 65 | 66 | return "index"; 67 | } 68 | 69 | //TODO: never gets called? 70 | @RequestMapping(value = "/login", method = RequestMethod.POST) 71 | //@RequestMapping(value = "/login") 72 | public String login(Model model, @ModelAttribute(value="login") AuthenticationRequest login) { 73 | logger.info("Logging in, user: " + login.getUsername()); 74 | //need to add account object to session? 75 | //CustomDetails userDetails = (CustomDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 76 | logger.debug("Principal: " + SecurityContextHolder.getContext(). 77 | getAuthentication().getPrincipal()); 78 | Map params = accountService.login(login); 79 | model.addAllAttributes(params); 80 | //logger.info("got user details, token: " + userDetails.getToken()); 81 | return "index"; 82 | } 83 | 84 | @RequestMapping(value = "/login", method = RequestMethod.GET) 85 | public String getLogin(Model model, @ModelAttribute(value="login") AuthenticationRequest login) { 86 | logger.info("Logging in GET, user: " + login.getUsername()); 87 | return "index"; 88 | } 89 | 90 | @RequestMapping(value="/logout", method = RequestMethod.POST) 91 | public String postLogout(Model model, @ModelAttribute(value="login") AuthenticationRequest login) { 92 | logger.info("Logout, user: " + login.getUsername()); 93 | logger.info(model.asMap().toString()); 94 | return "index"; 95 | } 96 | 97 | @RequestMapping(value = "/registration", method = RequestMethod.GET) 98 | public String registration(Model model) { 99 | Account account = new Account(); 100 | account.setBalance(new BigDecimal(100000)); 101 | model.addAttribute("account", account); 102 | return "registration"; 103 | } 104 | 105 | @RequestMapping(value = "/registration", method = RequestMethod.POST) 106 | public String register(Model model, @ModelAttribute(value="account") Account account) { 107 | logger.info("register: user:" + account.getUserid()); 108 | 109 | //need to set some stuff on account... 110 | 111 | account.setOpenbalance(account.getBalance()); 112 | account.setCreationdate(new Date()); 113 | 114 | AuthenticationRequest login = new AuthenticationRequest(); 115 | login.setUsername(account.getUserid()); 116 | model.addAttribute("login", login); 117 | model.addAttribute("marketSummary", summaryService.getMarketSummary()); 118 | accountService.createAccount(account); 119 | return "index"; 120 | } 121 | @ExceptionHandler({ Exception.class }) 122 | public ModelAndView error(HttpServletRequest req, Exception exception) { 123 | logger.debug("Handling error: " + exception); 124 | logger.warn("Exception:", exception); 125 | ModelAndView model = new ModelAndView(); 126 | model.addObject("errorCode", exception.getMessage()); 127 | model.addObject("errorMessage", exception); 128 | model.setViewName("error"); 129 | return model; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Account.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | import com.fasterxml.jackson.annotation.JsonFormat; 9 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 10 | import com.fasterxml.jackson.annotation.JsonProperty; 11 | 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | public class Account { 14 | 15 | @JsonProperty("id") 16 | private Integer id; 17 | 18 | @JsonProperty("address") 19 | private String address; 20 | 21 | @JsonProperty("passwd") 22 | private String passwd; 23 | 24 | @JsonProperty("userid") 25 | private String userid; 26 | 27 | @JsonProperty("email") 28 | private String email; 29 | 30 | @JsonProperty("creditcard") 31 | private String creditcard; 32 | 33 | @JsonProperty("fullname") 34 | private String fullname; 35 | 36 | @JsonProperty("authtoken") 37 | private String authtoken; 38 | 39 | @JsonProperty("creationdate") 40 | @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss") 41 | private Date creationdate; 42 | 43 | @JsonProperty("openbalance") 44 | private BigDecimal openbalance; 45 | 46 | @JsonProperty("logoutcount") 47 | private Integer logoutcount; 48 | 49 | @JsonProperty("balance") 50 | private BigDecimal balance; 51 | 52 | @JsonProperty("lastlogin") 53 | @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss") 54 | private Date lastlogin; 55 | 56 | @JsonProperty("logincount") 57 | private Integer logincount; 58 | 59 | public Integer getId() { 60 | return id; 61 | } 62 | 63 | public void setId(Integer id) { 64 | this.id = id; 65 | } 66 | 67 | public String getAddress() { 68 | return address; 69 | } 70 | 71 | public void setAddress(String address) { 72 | this.address = address; 73 | } 74 | 75 | public String getPasswd() { 76 | return passwd; 77 | } 78 | 79 | public void setPasswd(String passwd) { 80 | this.passwd = passwd; 81 | } 82 | 83 | public String getUserid() { 84 | return userid; 85 | } 86 | 87 | public void setUserid(String userid) { 88 | this.userid = userid; 89 | } 90 | 91 | public String getEmail() { 92 | return email; 93 | } 94 | 95 | public void setEmail(String email) { 96 | this.email = email; 97 | } 98 | 99 | public String getCreditcard() { 100 | return creditcard; 101 | } 102 | 103 | public void setCreditcard(String creditcard) { 104 | this.creditcard = creditcard; 105 | } 106 | 107 | public String getFullname() { 108 | return fullname; 109 | } 110 | 111 | public void setFullname(String fullname) { 112 | this.fullname = fullname; 113 | } 114 | 115 | public String getAuthtoken() { 116 | return authtoken; 117 | } 118 | 119 | public void setAuthtoken(String authtoken) { 120 | this.authtoken = authtoken; 121 | } 122 | 123 | public Date getCreationdate() { 124 | return creationdate; 125 | } 126 | 127 | public void setCreationdate(Date creationdate) { 128 | this.creationdate = creationdate; 129 | } 130 | 131 | public BigDecimal getOpenbalance() { 132 | return openbalance; 133 | } 134 | 135 | public void setOpenbalance(BigDecimal openbalance) { 136 | this.openbalance = openbalance; 137 | } 138 | 139 | public Integer getLogoutcount() { 140 | return logoutcount; 141 | } 142 | 143 | public void setLogoutcount(Integer logoutcount) { 144 | this.logoutcount = logoutcount; 145 | } 146 | 147 | public BigDecimal getBalance() { 148 | return balance; 149 | } 150 | 151 | public void setBalance(BigDecimal balance) { 152 | this.balance = balance; 153 | } 154 | 155 | public Date getLastlogin() { 156 | return lastlogin; 157 | } 158 | 159 | public void setLastlogin(Date lastlogin) { 160 | this.lastlogin = lastlogin; 161 | } 162 | 163 | public Integer getLogincount() { 164 | return logincount; 165 | } 166 | 167 | public void setLogincount(Integer logincount) { 168 | this.logincount = logincount; 169 | } 170 | 171 | @Override 172 | public String toString() { 173 | StringBuilder builder = new StringBuilder(); 174 | builder.append("Account [id=").append(id).append(", address=") 175 | .append(address).append(", passwd=").append("OMMITTED") 176 | .append(", userid=").append(userid).append(", email=") 177 | .append(email).append(", creditcard=").append(creditcard) 178 | .append(", fullname=").append(fullname).append(", authtoken=") 179 | .append(authtoken).append(", creationdate=") 180 | .append(creationdate).append(", openbalance=") 181 | .append(openbalance).append(", logoutcount=") 182 | .append(logoutcount).append(", balance=").append(balance) 183 | .append(", lastlogin=").append(lastlogin) 184 | .append(", logincount=").append(logincount).append("]"); 185 | return builder.toString(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 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 | package io.pivotal.web.domain; 17 | 18 | /** 19 | * AuthenticationRequest 20 | * @author Brian Dussault 21 | */ 22 | public class AuthenticationRequest { 23 | private String username; 24 | private String password; 25 | 26 | public String getUsername() { 27 | return username; 28 | } 29 | 30 | public void setUsername(String username) { 31 | this.username = username; 32 | } 33 | 34 | public String getPassword() { 35 | return password; 36 | } 37 | 38 | public void setPassword(String password) { 39 | this.password = password; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/CompanyInfo.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Represents company information. 7 | * 8 | * @author dpinto 9 | * 10 | */ 11 | public class CompanyInfo { 12 | /* 13 | * { 14 | "Symbol":"NFLX", 15 | "Name":"Netflix Inc", 16 | "Exchange":"NASDAQ" 17 | } 18 | */ 19 | @JsonProperty("Symbol") 20 | private String symbol; 21 | @JsonProperty("Name") 22 | private String name; 23 | @JsonProperty("Exchange") 24 | private String exchange; 25 | 26 | public String getSymbol() { 27 | return symbol; 28 | } 29 | public void setSymbol(String symbol) { 30 | this.symbol = symbol; 31 | } 32 | public String getName() { 33 | return name; 34 | } 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | public String getExchange() { 39 | return exchange; 40 | } 41 | public void setExchange(String exchange) { 42 | this.exchange = exchange; 43 | } 44 | @Override 45 | public String toString() { 46 | StringBuilder builder = new StringBuilder(); 47 | builder.append("CompanyInfo [symbol=").append(symbol).append(", name=") 48 | .append(name).append(", exchange=").append(exchange) 49 | .append("]"); 50 | return builder.toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Holding.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public class Holding { 8 | 9 | private Integer id; 10 | private String symbol; 11 | private Integer quantity = 0; 12 | private BigDecimal purchaseValue = BigDecimal.ZERO; 13 | private BigDecimal sellValue = BigDecimal.ZERO; 14 | private Set orders = new HashSet<>(); 15 | private BigDecimal currentValue = BigDecimal.ZERO; 16 | public Integer getId() { 17 | return id; 18 | } 19 | public void setId(Integer id) { 20 | this.id = id; 21 | } 22 | public String getSymbol() { 23 | return symbol; 24 | } 25 | public void setSymbol(String symbol) { 26 | this.symbol = symbol; 27 | } 28 | public Integer getQuantity() { 29 | return quantity; 30 | } 31 | public void setQuantity(Integer quantity) { 32 | this.quantity = quantity; 33 | } 34 | public BigDecimal getPurchaseValue() { 35 | return purchaseValue; 36 | } 37 | public void setPurchaseValue(BigDecimal purchaseValue) { 38 | this.purchaseValue = purchaseValue; 39 | } 40 | public Set getOrders() { 41 | return orders; 42 | } 43 | public void setOrders(Set orders) { 44 | this.orders = orders; 45 | } 46 | public BigDecimal getCurrentValue() { 47 | return currentValue; 48 | } 49 | public void setCurrentValue(BigDecimal currentValue) { 50 | this.currentValue = currentValue; 51 | } 52 | 53 | public void addOrder(Order order) { 54 | //check order is not already in. 55 | if (orders.contains(order)) { 56 | //TODO: throw RuntimeException?? and do nothing; 57 | } else { 58 | orders.add(order); 59 | //update stats 60 | if (order.getOrderType().equals(OrderType.BUY)) { 61 | setQuantity(getQuantity()+order.getQuantity()); 62 | setPurchaseValue(getPurchaseValue().add(order.getPrice().multiply(new BigDecimal(order.getQuantity())))); 63 | } else if (order.getOrderType().equals(OrderType.SELL)) { 64 | setQuantity(getQuantity()-order.getQuantity()); 65 | setSellValue(getSellValue().add(order.getPrice().multiply(new BigDecimal(order.getQuantity())))); 66 | } 67 | } 68 | } 69 | 70 | public BigDecimal getSellValue() { 71 | return sellValue; 72 | } 73 | public void setSellValue(BigDecimal sellPrice) { 74 | this.sellValue = sellPrice; 75 | } 76 | @Override 77 | public String toString() { 78 | StringBuilder builder = new StringBuilder(); 79 | builder.append("Holding [id=").append(id).append(", symbol=") 80 | .append(symbol).append(", quantity=").append(quantity) 81 | .append(", purchasePrice=").append(purchaseValue) 82 | .append(", sellPrice=").append(sellValue).append(", orders=") 83 | .append(orders).append(", currentValue=").append(currentValue) 84 | .append("]"); 85 | return builder.toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/MarketSummary.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | public class MarketSummary { 8 | 9 | private List topLosers; 10 | 11 | private List topGainers; 12 | 13 | private BigDecimal tradeStockIndexAverage = BigDecimal.ZERO; 14 | 15 | private BigDecimal tradeStockIndexVolume = BigDecimal.ZERO; 16 | 17 | private BigDecimal tradeStockIndexOpenAverage = BigDecimal.ZERO; 18 | 19 | private BigDecimal change = BigDecimal.ZERO; 20 | 21 | private BigDecimal percentGain = BigDecimal.ZERO; 22 | 23 | private Date summaryDate = new Date(); 24 | 25 | public List getTopLosers() { 26 | return topLosers; 27 | } 28 | 29 | public void setTopLosers(List topLosers) { 30 | this.topLosers = topLosers; 31 | } 32 | 33 | public List getTopGainers() { 34 | return topGainers; 35 | } 36 | 37 | public void setTopGainers(List topGainers) { 38 | this.topGainers = topGainers; 39 | } 40 | 41 | public BigDecimal getTradeStockIndexAverage() { 42 | return tradeStockIndexAverage; 43 | } 44 | 45 | public void setTradeStockIndexAverage(BigDecimal tradeStockIndexAverage) { 46 | this.tradeStockIndexAverage = tradeStockIndexAverage; 47 | } 48 | 49 | public BigDecimal getTradeStockIndexVolume() { 50 | return tradeStockIndexVolume; 51 | } 52 | 53 | public void setTradeStockIndexVolume(BigDecimal tradeStockIndexVolume) { 54 | this.tradeStockIndexVolume = tradeStockIndexVolume; 55 | } 56 | 57 | public BigDecimal getTradeStockIndexOpenAverage() { 58 | return tradeStockIndexOpenAverage; 59 | } 60 | 61 | public void setTradeStockIndexOpenAverage(BigDecimal tradeStockIndexOpenAverage) { 62 | this.tradeStockIndexOpenAverage = tradeStockIndexOpenAverage; 63 | } 64 | 65 | public BigDecimal getChange() { 66 | return change; 67 | } 68 | 69 | public void setChange(BigDecimal change) { 70 | this.change = change; 71 | } 72 | 73 | public BigDecimal getPercentGain() { 74 | return percentGain; 75 | } 76 | 77 | public void setPercentGain(BigDecimal percentGain) { 78 | this.percentGain = percentGain; 79 | } 80 | 81 | public Date getSummaryDate() { 82 | return summaryDate; 83 | } 84 | 85 | public void setSummaryDate(Date summaryDate) { 86 | this.summaryDate = summaryDate; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | StringBuilder builder = new StringBuilder(); 92 | builder.append("MarketSummary [topLosers=").append(topLosers) 93 | .append(", topGainers=").append(topGainers) 94 | .append(", tradeStockIndexAverage=") 95 | .append(tradeStockIndexAverage) 96 | .append(", tradeStockIndexVolume=") 97 | .append(tradeStockIndexVolume) 98 | .append(", tradeStockIndexOpenAverage=") 99 | .append(tradeStockIndexOpenAverage).append(", change=") 100 | .append(change).append(", percentGain=").append(percentGain) 101 | .append(", summaryDate=").append(summaryDate).append("]"); 102 | return builder.toString(); 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Order.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | 6 | public class Order { 7 | 8 | private Integer orderId; 9 | 10 | private String accountId; 11 | 12 | private String symbol; 13 | 14 | private BigDecimal orderFee; 15 | 16 | private Date completionDate; 17 | 18 | private OrderType orderType; 19 | 20 | private BigDecimal price; 21 | 22 | private Integer quantity; 23 | 24 | public Integer getOrderId() { 25 | return orderId; 26 | } 27 | 28 | public void setOrderId(Integer orderId) { 29 | this.orderId = orderId; 30 | } 31 | 32 | public String getAccountId() { 33 | return accountId; 34 | } 35 | 36 | public void setAccountId(String accountId) { 37 | this.accountId = accountId; 38 | } 39 | 40 | public String getSymbol() { 41 | return symbol; 42 | } 43 | 44 | public void setSymbol(String symbol) { 45 | this.symbol = symbol; 46 | } 47 | 48 | public BigDecimal getOrderFee() { 49 | return orderFee; 50 | } 51 | 52 | public void setOrderFee(BigDecimal orderFee) { 53 | this.orderFee = orderFee; 54 | } 55 | 56 | public Date getCompletionDate() { 57 | return completionDate; 58 | } 59 | 60 | public void setCompletionDate(Date completionDate) { 61 | this.completionDate = completionDate; 62 | } 63 | 64 | public OrderType getOrderType() { 65 | return orderType; 66 | } 67 | 68 | public void setOrderType(OrderType orderType) { 69 | this.orderType = orderType; 70 | } 71 | 72 | public BigDecimal getPrice() { 73 | return price; 74 | } 75 | 76 | public void setPrice(BigDecimal price) { 77 | this.price = price; 78 | } 79 | 80 | public Integer getQuantity() { 81 | return quantity; 82 | } 83 | 84 | public void setQuantity(Integer quantity) { 85 | this.quantity = quantity; 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | StringBuilder builder = new StringBuilder(); 91 | builder.append("Order [orderId=").append(orderId) 92 | .append(", accountId=").append(accountId).append(", symbol=") 93 | .append(symbol).append(", orderFee=").append(orderFee) 94 | .append(", completionDate=").append(completionDate) 95 | .append(", orderType=").append(orderType).append(", price=") 96 | .append(price).append(", quantity=").append(quantity) 97 | .append("]"); 98 | return builder.toString(); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | final int prime = 31; 104 | int result = 1; 105 | result = prime * result 106 | + ((accountId == null) ? 0 : accountId.hashCode()); 107 | result = prime * result 108 | + ((completionDate == null) ? 0 : completionDate.hashCode()); 109 | result = prime * result 110 | + ((orderFee == null) ? 0 : orderFee.hashCode()); 111 | result = prime * result + ((orderId == null) ? 0 : orderId.hashCode()); 112 | result = prime * result 113 | + ((orderType == null) ? 0 : orderType.hashCode()); 114 | result = prime * result + ((price == null) ? 0 : price.hashCode()); 115 | result = prime * result 116 | + ((quantity == null) ? 0 : quantity.hashCode()); 117 | result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); 118 | return result; 119 | } 120 | 121 | @Override 122 | public boolean equals(Object obj) { 123 | if (this == obj) 124 | return true; 125 | if (obj == null) 126 | return false; 127 | if (getClass() != obj.getClass()) 128 | return false; 129 | Order other = (Order) obj; 130 | if (accountId == null) { 131 | if (other.accountId != null) 132 | return false; 133 | } else if (!accountId.equals(other.accountId)) 134 | return false; 135 | if (completionDate == null) { 136 | if (other.completionDate != null) 137 | return false; 138 | } else if (!completionDate.equals(other.completionDate)) 139 | return false; 140 | if (orderFee == null) { 141 | if (other.orderFee != null) 142 | return false; 143 | } else if (!orderFee.equals(other.orderFee)) 144 | return false; 145 | if (orderId == null) { 146 | if (other.orderId != null) 147 | return false; 148 | } else if (!orderId.equals(other.orderId)) 149 | return false; 150 | if (orderType != other.orderType) 151 | return false; 152 | if (price == null) { 153 | if (other.price != null) 154 | return false; 155 | } else if (!price.equals(other.price)) 156 | return false; 157 | if (quantity == null) { 158 | if (other.quantity != null) 159 | return false; 160 | } else if (!quantity.equals(other.quantity)) 161 | return false; 162 | if (symbol == null) { 163 | if (other.symbol != null) 164 | return false; 165 | } else if (!symbol.equals(other.symbol)) 166 | return false; 167 | return true; 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/OrderType.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | /** 4 | * The type of the order. It can be a BUY order or a SELL order. 5 | * 6 | * @author David Ferreira Pinto 7 | * 8 | */ 9 | public enum OrderType { 10 | BUY,SELL 11 | } 12 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Portfolio.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class Portfolio { 10 | 11 | private String accountId; 12 | private String name; 13 | private BigDecimal currentTotalValue = BigDecimal.ZERO; 14 | private BigDecimal purchaseValue = BigDecimal.ZERO; 15 | private BigDecimal sellValue = BigDecimal.ZERO; 16 | private Map holdings = new HashMap<>(); 17 | public String getAccountId() { 18 | return accountId; 19 | } 20 | public void setAccountId(String accountId) { 21 | this.accountId = accountId; 22 | } 23 | public String getName() { 24 | return name; 25 | } 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | public Map getHoldings() { 30 | return holdings; 31 | } 32 | public void setHoldings(Map holdings) { 33 | this.holdings = holdings; 34 | } 35 | 36 | public void addHolding(Holding holding) { 37 | holdings.put(holding.getSymbol(),holding); 38 | } 39 | 40 | public Holding getHolding(String symbol) { 41 | return holdings.get(symbol); 42 | } 43 | 44 | public BigDecimal getCurrentTotalValue() { 45 | return currentTotalValue; 46 | } 47 | public void setCurrentTotalValue(BigDecimal currentTotalValue) { 48 | this.currentTotalValue = currentTotalValue; 49 | } 50 | 51 | public void refreshTotalValue() { 52 | this.currentTotalValue = BigDecimal.ZERO; 53 | holdings.values().forEach(holding -> { 54 | this.currentTotalValue = this.currentTotalValue.add(holding.getCurrentValue().multiply(new BigDecimal(holding.getQuantity()))); 55 | this.purchaseValue = this.purchaseValue.add(holding.getPurchaseValue()); 56 | this.sellValue = this.sellValue.add(holding.getSellValue()); 57 | }); 58 | } 59 | 60 | public BigDecimal getSellValue() { 61 | return sellValue; 62 | } 63 | public void setSellValue(BigDecimal sellValue) { 64 | this.sellValue = sellValue; 65 | } 66 | @Override 67 | public String toString() { 68 | StringBuilder builder = new StringBuilder(); 69 | builder.append("Portfolio [accountId=").append(accountId) 70 | .append(", name=").append(name).append(", currentTotalValue=") 71 | .append(currentTotalValue).append(", purchaseValue=") 72 | .append(purchaseValue).append(", sellValue=").append(sellValue) 73 | .append(", holdings=").append(holdings).append("]"); 74 | return builder.toString(); 75 | } 76 | public BigDecimal getPurchaseValue() { 77 | return purchaseValue; 78 | } 79 | public void setPurchaseValue(BigDecimal purchaseValue) { 80 | this.purchaseValue = purchaseValue; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Quote.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | public class Quote { 9 | @JsonProperty("Status") 10 | private String status; 11 | @JsonProperty("Name") 12 | private String name; 13 | @JsonProperty("Symbol") 14 | private String symbol; 15 | @JsonProperty("LastPrice") 16 | private Double lastPrice = 0d; 17 | @JsonProperty("Change") 18 | private Double change = 0d; 19 | @JsonProperty("ChangePercent") 20 | private Double changePercent = 0d; 21 | @JsonProperty("Timestamp") 22 | @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="EEE MMM dd HH:mm:ss zzzXXX yyyy") 23 | private Date timestamp; 24 | @JsonProperty("MSDate") 25 | private Double mSDate; 26 | @JsonProperty("MarketCap") 27 | private Double marketCap = 0d; 28 | @JsonProperty("Volume") 29 | private Integer volume = 0; 30 | @JsonProperty("ChangeYTD") 31 | private Double changeYTD = 0d; 32 | @JsonProperty("ChangePercentYTD") 33 | private Double changePercentYTD = 0d; 34 | @JsonProperty("High") 35 | private Double high = 0d; 36 | @JsonProperty("Low") 37 | private Double low = 0d; 38 | @JsonProperty("Open") 39 | private Double open = 0d; 40 | public String getStatus() { 41 | return status; 42 | } 43 | public void setStatus(String status) { 44 | this.status = status; 45 | } 46 | public String getName() { 47 | return name; 48 | } 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | public String getSymbol() { 53 | return symbol; 54 | } 55 | public void setSymbol(String symbol) { 56 | this.symbol = symbol; 57 | } 58 | public Double getLastPrice() { 59 | return lastPrice; 60 | } 61 | public void setLastPrice(Double lastPrice) { 62 | this.lastPrice = lastPrice; 63 | } 64 | public Double getChange() { 65 | return change; 66 | } 67 | public void setChange(Double change) { 68 | this.change = change; 69 | } 70 | public Double getChangePercent() { 71 | return changePercent; 72 | } 73 | public void setChangePercent(Double changePercent) { 74 | this.changePercent = changePercent; 75 | } 76 | public Date getTimestamp() { 77 | return timestamp; 78 | } 79 | public void setTimestamp(Date timestamp) { 80 | this.timestamp = timestamp; 81 | } 82 | public Double getmSDate() { 83 | return mSDate; 84 | } 85 | public void setmSDate(Double mSDate) { 86 | this.mSDate = mSDate; 87 | } 88 | public Double getMarketCap() { 89 | return marketCap; 90 | } 91 | public void setMarketCap(Double marketCap) { 92 | this.marketCap = marketCap; 93 | } 94 | public Integer getVolume() { 95 | return volume; 96 | } 97 | public void setVolume(Integer volume) { 98 | this.volume = volume; 99 | } 100 | public Double getChangeYTD() { 101 | return changeYTD; 102 | } 103 | public void setChangeYTD(Double changeYTD) { 104 | this.changeYTD = changeYTD; 105 | } 106 | public Double getChangePercentYTD() { 107 | return changePercentYTD; 108 | } 109 | public void setChangePercentYTD(Double changePercentYTD) { 110 | this.changePercentYTD = changePercentYTD; 111 | } 112 | public Double getHigh() { 113 | return high; 114 | } 115 | public void setHigh(Double high) { 116 | this.high = high; 117 | } 118 | public Double getLow() { 119 | return low; 120 | } 121 | public void setLow(Double low) { 122 | this.low = low; 123 | } 124 | public Double getOpen() { 125 | return open; 126 | } 127 | public void setOpen(Double open) { 128 | this.open = open; 129 | } 130 | @Override 131 | public String toString() { 132 | StringBuilder builder = new StringBuilder(); 133 | builder.append("Quote [status=").append(status).append(", name=") 134 | .append(name).append(", symbol=").append(symbol) 135 | .append(", lastPrice=").append(lastPrice).append(", change=") 136 | .append(change).append(", changePercent=") 137 | .append(changePercent).append(", timestamp=").append(timestamp) 138 | .append(", mSDate=").append(mSDate).append(", marketCap=") 139 | .append(marketCap).append(", volume=").append(volume) 140 | .append(", changeYTD=").append(changeYTD) 141 | .append(", changePercentYTD=").append(changePercentYTD) 142 | .append(", high=").append(high).append(", low=").append(low) 143 | .append(", open=").append(open).append("]"); 144 | return builder.toString(); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/domain/Search.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.domain; 2 | 3 | public class Search { 4 | 5 | private String name; 6 | 7 | public String getName() { 8 | return name; 9 | } 10 | 11 | public void setName(String name) { 12 | this.name = name; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/exception/OrderNotSavedException.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.exception; 2 | 3 | public class OrderNotSavedException extends RuntimeException { 4 | /** 5 | * 6 | */ 7 | private static final long serialVersionUID = 6269912483691692397L; 8 | 9 | public OrderNotSavedException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/security/CustomAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.security; 2 | 3 | import io.pivotal.web.domain.AuthenticationRequest; 4 | import io.pivotal.web.service.UserService; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.security.authentication.AuthenticationProvider; 12 | import org.springframework.security.authentication.BadCredentialsException; 13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.AuthenticationException; 16 | import org.springframework.security.core.GrantedAuthority; 17 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 18 | import org.springframework.stereotype.Component; 19 | import org.springframework.web.client.HttpServerErrorException; 20 | 21 | @Component 22 | public class CustomAuthenticationProvider implements AuthenticationProvider { 23 | 24 | @Autowired 25 | private UserService service; 26 | 27 | @Override 28 | public Authentication authenticate(Authentication authentication) 29 | throws AuthenticationException { 30 | String name = authentication.getName(); 31 | String password = authentication.getCredentials().toString(); 32 | AuthenticationRequest request = new AuthenticationRequest(); 33 | request.setUsername(name); 34 | request.setPassword(password); 35 | try { 36 | Map params = service.login(request); 37 | if (params != null) { 38 | List grantedAuths = new ArrayList<>(); 39 | grantedAuths.add(new SimpleGrantedAuthority("USER")); 40 | Authentication auth = new UsernamePasswordAuthenticationToken( 41 | name, password, grantedAuths); 42 | return auth; 43 | } else { 44 | throw new BadCredentialsException("Username not found"); 45 | } 46 | } catch (HttpServerErrorException e) { 47 | throw new BadCredentialsException("Login failed!"); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean supports(Class authentication) { 53 | return authentication.equals(UsernamePasswordAuthenticationToken.class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/security/CustomCredentialsService.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.security; 2 | 3 | import io.pivotal.web.domain.Account; 4 | import io.pivotal.web.service.UserService; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 14 | import org.springframework.security.core.userdetails.UserDetails; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 17 | import org.springframework.stereotype.Component; 18 | 19 | @Component 20 | public class CustomCredentialsService implements UserDetailsService { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(CustomCredentialsService.class); 23 | 24 | @Autowired 25 | private UserService accountservice; 26 | 27 | @Override 28 | public UserDetails loadUserByUsername(String username) 29 | throws UsernameNotFoundException { 30 | logger.info("Looking for user: " + username); 31 | 32 | if (username == null) { 33 | logger.warn("username is null: "); 34 | throw new UsernameNotFoundException(username); 35 | } 36 | 37 | Account account = accountservice.getAccount(username); 38 | logger.info("Got account in credentials: " + account); 39 | UserDetails custom = new CustomDetails(account); 40 | 41 | return custom; 42 | } 43 | 44 | public class CustomDetails implements UserDetails { 45 | private static final String ROLE = "USER"; 46 | private Account account; 47 | 48 | public CustomDetails(Account account) { 49 | this.account = account; 50 | } 51 | 52 | @Override 53 | public Collection getAuthorities() { 54 | Collection authorities = new ArrayList<>(); 55 | SimpleGrantedAuthority authority = new SimpleGrantedAuthority(ROLE); 56 | authorities.add(authority); 57 | return authorities; 58 | } 59 | 60 | @Override 61 | public String getPassword() { 62 | return account.getPasswd(); 63 | } 64 | 65 | @Override 66 | public String getUsername() { 67 | return account.getUserid(); 68 | } 69 | 70 | @Override 71 | public boolean isAccountNonExpired() { 72 | return true; 73 | } 74 | 75 | @Override 76 | public boolean isAccountNonLocked() { 77 | return true; 78 | } 79 | 80 | @Override 81 | public boolean isCredentialsNonExpired() { 82 | return true; 83 | } 84 | 85 | @Override 86 | public boolean isEnabled() { 87 | return true; 88 | } 89 | 90 | public String getToken() { 91 | return account.getAuthtoken(); 92 | } 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/security/LogoutSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.security; 2 | 3 | import io.pivotal.web.service.UserService; 4 | 5 | import java.io.IOException; 6 | import java.util.Enumeration; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; 17 | import org.springframework.stereotype.Component; 18 | 19 | @Component 20 | public class LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { 21 | private static final Logger logger = LoggerFactory 22 | .getLogger(LogoutSuccessHandler.class); 23 | @Autowired 24 | private UserService service; 25 | 26 | public LogoutSuccessHandler() { 27 | super(); 28 | } 29 | // Just for setting the default target URL 30 | public LogoutSuccessHandler(String defaultTargetURL) { 31 | this.setDefaultTargetUrl(defaultTargetURL); 32 | } 33 | 34 | @Override 35 | public void onLogoutSuccess(HttpServletRequest request, 36 | HttpServletResponse response, Authentication authentication) 37 | throws IOException, ServletException { 38 | 39 | service.logout(authentication.getPrincipal().toString()); 40 | super.onLogoutSuccess(request, response, authentication); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/service/MarketService.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; 10 | import io.pivotal.web.domain.CompanyInfo; 11 | import io.pivotal.web.domain.Order; 12 | import io.pivotal.web.domain.Portfolio; 13 | import io.pivotal.web.domain.Quote; 14 | import io.pivotal.web.exception.OrderNotSavedException; 15 | 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 21 | import org.springframework.cloud.context.config.annotation.RefreshScope; 22 | import org.springframework.core.ParameterizedTypeReference; 23 | import org.springframework.http.HttpStatus; 24 | import org.springframework.http.ResponseEntity; 25 | import org.springframework.stereotype.Service; 26 | import org.springframework.web.client.RestTemplate; 27 | 28 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 29 | 30 | 31 | @Service 32 | @RefreshScope 33 | public class MarketService { 34 | private static final Logger logger = LoggerFactory.getLogger(MarketService.class); 35 | 36 | @Autowired 37 | @LoadBalanced 38 | private RestTemplate restTemplate; 39 | 40 | @Value("${pivotal.quotesService.name}") 41 | private String quotesService; 42 | 43 | @Value("${pivotal.portfolioService.name}") 44 | private String portfolioService; 45 | 46 | @HystrixCommand(fallbackMethod = "getCompaniesFallback") 47 | public List getCompanies(String name) { 48 | logger.debug("Fetching companies with name or symbol matching: " + name); 49 | CompanyInfo[] infos = restTemplate.getForObject("http://" + quotesService + "/company/{name}", CompanyInfo[].class, name); 50 | return Arrays.asList(infos); 51 | } 52 | 53 | @SuppressWarnings("unused") 54 | private List getCompaniesFallback(String name) { 55 | return new ArrayList<>(); 56 | } 57 | 58 | /** 59 | * Retrieve multiple quotes. 60 | * 61 | * @param symbols comma separated list of symbols. 62 | * @return 63 | */ 64 | @HystrixCommand(fallbackMethod = "getQuotesFallback", 65 | commandProperties = {@HystrixProperty(name="execution.timeout.enabled", value="false")}) 66 | public List getQuotes(String symbols) { 67 | logger.debug("retrieving multiple quotes: " + symbols); 68 | Quote[] quotesArr = restTemplate.getForObject("http://" + quotesService + "/quotes?q={symbols}", Quote[].class, symbols); 69 | List quotes = Arrays.asList(quotesArr); 70 | logger.debug("Received quotes: {}",quotes); 71 | return quotes; 72 | } 73 | 74 | @SuppressWarnings("unused") 75 | private List getQuotesFallback(String symbols) { 76 | List result = new ArrayList<>(); 77 | String[] splitSymbols = symbols.split(","); 78 | 79 | for (String symbol : splitSymbols) { 80 | Quote quote = new Quote(); 81 | quote.setSymbol(symbol); 82 | quote.setStatus("FAILED"); 83 | result.add( quote ); 84 | } 85 | return result; 86 | } 87 | 88 | public List getQuotes(String[] symbols) { 89 | logger.debug("Fetching multiple quotes array: {} ",Arrays.asList(symbols)); 90 | 91 | return getQuotes(Arrays.asList(symbols)); 92 | } 93 | 94 | public List getQuotes(Collection symbols) { 95 | logger.debug("Fetching multiple quotes array: {} ",symbols); 96 | StringBuilder builder = new StringBuilder(); 97 | for (Iterator i = symbols.iterator(); i.hasNext();) { 98 | builder.append(i.next()); 99 | if (i.hasNext()) { 100 | builder.append(","); 101 | } 102 | } 103 | return getQuotes(builder.toString()); 104 | } 105 | 106 | public Order sendOrder(Order order ) throws OrderNotSavedException{ 107 | logger.debug("send order: " + order); 108 | 109 | //check result of http request to ensure its ok. 110 | 111 | ResponseEntity result = restTemplate.postForEntity("http://" + portfolioService + "/portfolio/{accountId}", order, Order.class, order.getAccountId()); 112 | if (result.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) { 113 | throw new OrderNotSavedException("Could not save the order"); 114 | } 115 | logger.debug("Order saved:: " + result.getBody()); 116 | return result.getBody(); 117 | } 118 | 119 | @HystrixCommand(fallbackMethod = "getPortfolioFallback") 120 | public Portfolio getPortfolio(String accountId) { 121 | Portfolio folio = restTemplate.getForObject("http://" + portfolioService + "/portfolio/{accountid}", Portfolio.class, accountId); 122 | logger.debug("Portfolio received: " + folio); 123 | return folio; 124 | } 125 | 126 | @SuppressWarnings("unused") 127 | private Portfolio getPortfolioFallback(String accountId) { 128 | logger.debug("Portfolio fallback"); 129 | Portfolio folio = new Portfolio(); 130 | folio.setAccountId(accountId); 131 | return folio; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/service/MarketSummaryService.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.service; 2 | 3 | import io.pivotal.web.domain.MarketSummary; 4 | import io.pivotal.web.domain.Quote; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.cloud.context.config.annotation.RefreshScope; 10 | import org.springframework.scheduling.annotation.Scheduled; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | @Service 19 | @RefreshScope 20 | public class MarketSummaryService { 21 | private static final Logger logger = LoggerFactory.getLogger(MarketSummaryService.class); 22 | 23 | @Value("${pivotal.summary.quotes:3}") 24 | private Integer numberOfQuotes; 25 | 26 | //10 minutes in milliseconds 27 | @Value("${pivotal.summary.refresh:600000}") 28 | private final static String refresh_period = "600000"; 29 | 30 | @Value("${pivotal.summary.symbols.it:EMC,IBM,VMW}") 31 | private String symbolsIT; 32 | @Value("${pivotal.summary.symbols.fs:JPM,C,MS}") 33 | private String symbolsFS; 34 | 35 | @Autowired 36 | private MarketService marketService; 37 | 38 | private MarketSummary summary = new MarketSummary(); 39 | 40 | public MarketSummary getMarketSummary() { 41 | logger.debug("Retrieving Market Summary: " + summary); 42 | 43 | return summary; 44 | } 45 | 46 | @Scheduled(fixedRateString = refresh_period) 47 | protected void retrieveMarketSummary() { 48 | logger.debug("Scheduled retrieval of Market Summary"); 49 | /* 50 | * Sleuth currently doesn't work with parallelStream. 51 | * TODO: re-implement parallel calls. 52 | */ 53 | //List quotesIT = pickRandomThree(Arrays.asList(symbolsIT.split(","))).parallelStream().map(symbol -> getQuote(symbol)).collect(Collectors.toList()); 54 | //List quotesFS = pickRandomThree(Arrays.asList(symbolsFS.split(","))).parallelStream().map(symbol -> getQuote(symbol)).collect(Collectors.toList()); 55 | 56 | //List quotesFS = pickRandomThree(Arrays.asList(symbolsFS.split(","))).stream().map(symbol -> marketService.getQuote(symbol)).collect(Collectors.toList()); 57 | summary.setTopGainers(getTopThree(symbolsIT)); 58 | summary.setTopLosers(getTopThree(symbolsFS)); 59 | } 60 | 61 | /** 62 | * Retrieve the list of top winners/losers. 63 | * Currently retrieving list of 3 random. 64 | */ 65 | private List getTopThree(String symbols) { 66 | StringBuilder builder = new StringBuilder(); 67 | for(Iterator i = pickRandomThree(Arrays.asList(symbols.split(","))).iterator(); i.hasNext();) { 68 | builder.append(i.next()); 69 | if (i.hasNext()) { 70 | builder.append(","); 71 | } 72 | } 73 | return marketService.getQuotes(builder.toString()); 74 | } 75 | 76 | private List pickRandomThree(List symbols) { 77 | Collections.shuffle(symbols); 78 | List list = symbols.subList(0, numberOfQuotes); 79 | return list; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/java/io/pivotal/web/service/UserService.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.service; 2 | 3 | import java.util.Map; 4 | 5 | import io.pivotal.web.domain.Account; 6 | import io.pivotal.web.domain.AuthenticationRequest; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 13 | import org.springframework.cloud.context.config.annotation.RefreshScope; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | @Service 19 | @RefreshScope 20 | public class UserService { 21 | private static final Logger logger = LoggerFactory 22 | .getLogger(UserService.class); 23 | 24 | @Autowired 25 | @LoadBalanced 26 | private RestTemplate restTemplate; 27 | 28 | @Value("${pivotal.accountsService.name}") 29 | private String accountsService; 30 | 31 | public void createAccount(Account account) { 32 | logger.debug("Saving account with userId: " + account.getUserid()); 33 | String status = restTemplate.postForObject("http://" + accountsService + "/account/", account, String.class); 34 | logger.info("Status from registering account for "+ account.getUserid()+ " is " + status); 35 | } 36 | 37 | public Map login(AuthenticationRequest request){ 38 | logger.debug("logging in with userId:" + request.getUsername()); 39 | Map result = (Map) restTemplate.postForObject("http://" + accountsService + "/login/".toString(), request, Map.class); 40 | return result; 41 | } 42 | 43 | //TODO: change to /account/{user} 44 | public Account getAccount(String user) { 45 | logger.debug("Looking for account with userId: " + user); 46 | 47 | Account account = restTemplate.getForObject("http://" + accountsService + "/account/?name={user}", Account.class, user); 48 | logger.debug("Got Account: " + account); 49 | return account; 50 | } 51 | 52 | public void logout(String user) { 53 | logger.debug("logging out account with userId: " + user); 54 | 55 | ResponseEntity response = restTemplate.getForEntity("http://" + accountsService + "/logout/{user}", String.class, user); 56 | logger.debug("Logout response: " + response.getStatusCode()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles.active: local 3 | application: 4 | name: web-service 5 | info: 6 | build: 7 | group: ${group} 8 | name: ${name} 9 | description: ${description} 10 | version: ${version} -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | a { 2 | cursor: pointer; 3 | } 4 | body { 5 | background-color: #ecefef; 6 | padding-top:110px; 7 | } 8 | #nc-loading .logo, #nc-loading .loading { 9 | margin: auto; 10 | } 11 | #nc-loading .loading { 12 | margin-top: 20px; 13 | } 14 | .loading { 15 | background:url(../images/loading.gif) no-repeat; 16 | width: 32px; 17 | height: 32px; 18 | } 19 | 20 | .btn-success { 21 | background-color: #138078; 22 | border-color: #333333; 23 | } 24 | .btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active { 25 | background-color: #0F635D; 26 | border-color: #333333; 27 | } 28 | .panel-default>.panel-heading { 29 | background-color: #138078; 30 | } 31 | .panel-title { 32 | color: white; 33 | } 34 | 35 | .completed { 36 | padding:2px 5px; 37 | background:#5daf1b; 38 | color:#ffffff; 39 | border-radius:3px; 40 | } 41 | .uncompleted { 42 | padding:2px 5px; 43 | background:#a09f9f; 44 | color:#ffffff; 45 | border-radius:3px; 46 | } 47 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.eot -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.ttf -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-bold-webfont.woff -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.eot -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.ttf -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-light-webfont.woff -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.eot -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.ttf -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colaborate-medium-webfont.woff -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.eot -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.ttf -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabreg-webfont.woff -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.eot -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.ttf -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/fonts/colabthi-webfont.woff -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/bg-container.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/bg-container.gif -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/bg-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/bg-small.gif -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/border-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/border-bg.jpg -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/button-transactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/button-transactions.png -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/icon-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/icon-arrow.png -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/icon-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/icon-custom.png -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davpin/cf-SpringBootTrader/83cbd17c266891e1bc0ce20792ef74162bb0098a/springboottrades-web/src/main/resources/static/images/logo.png -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error page 5 | 6 |

7 | 8 | 9 |

404

10 |

Error java.lang.NullPointerException

11 | Back to Home Page 12 | 13 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/account_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |

User Statistics

7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Full Nameuser
User ID / Emailuser
Creation Datetoday
Total Logins0
Session Creation Datetoday
Open Balanceuser
35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/header_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/login_fragment.html: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 |
6 |
7 |
8 |

Login

9 |
10 |
11 |
12 |
13 |
Invalid username and password.
14 |
15 |
16 |
You have been logged out.
17 |
18 |
19 |
20 | 21 |
22 |
23 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |

Registration

45 |
46 |
47 |

Don't have a trader account yet?

48 |

Create One

49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/marketsummary_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 |
35 |
36 |
Index:
Volume:
Change: 49 | 50 |
53 |
54 |
55 | 56 | 57 |
58 |

59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
SymbolPriceChangeHighLow
76 |
77 |
78 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/navbar_fragment.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 |
6 | 17 | 60 |
61 |
62 | 63 | 64 |
65 | 76 | 85 |
86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/registration_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |

7 | Registration   Please enter test data for creating the account 8 |

9 |
10 |
11 |
12 |
13 | x 14 |

15 |

16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | Full Name error 26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 | 34 |
Email Error 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 | 43 |
Password Error 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 |
Password Confirmation error 53 |
54 |
55 |
56 |
57 |
58 |
59 | 60 |
61 |
62 | 63 |
UsernameError 64 |
65 |
66 |
67 |
68 | 69 |
70 |
71 | 72 |
Open Balance Error 73 |
74 |
75 |
76 |
77 | 78 |
79 |
80 | 81 |
CreditCardError 82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 | 90 |
AddressError 91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |

Login

105 |
106 |
107 |

If you have already registered, please enter your login and password on our login page.

108 |

109 | login 110 |

111 |
112 |
113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/fragments/trade_fragment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |   12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |
21 |
22 |

Quotes - result(s)

23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 54 | 55 | 56 |
Company nameSymbolPriceChangeChange YTDHighLowQuantity
48 | 49 | 50 | 51 | Buy 53 |
57 |
58 |
59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |

Order Successful

67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
TypeSymbolPriceQuantityFeeTOTAL
symbolsymbolpricequantityFeetotal
90 |
91 |
92 |
93 |
94 | 95 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Spring Trader 8 | 9 |
10 | 11 | 12 | 13 | 16 | 19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/portfolio.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Spring Trader - Portfolio 7 |
8 | 9 | 10 | 11 | 12 | 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/registration.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Spring Trader - Registration 7 |
8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /springboottrades-web/src/main/resources/templates/trade.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Spring Trader 7 |
8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /springboottrades-web/src/test/java/io/pivotal/web/WebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.web.WebAppConfiguration; 6 | import org.springframework.boot.test.SpringApplicationConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringApplicationConfiguration(classes = WebApplication.class) 11 | @WebAppConfiguration 12 | public class WebApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /springboottrades-web/src/test/java/io/pivotal/web/service/MarketServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.web.service; 2 | 3 | 4 | import org.junit.Before; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.MockitoAnnotations; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | /** 13 | * Tests the QuoteService. 14 | * @author David Ferreira Pinto 15 | * 16 | */ 17 | public class MarketServiceTest { 18 | MockMvc mockMvc; 19 | 20 | @InjectMocks 21 | MarketService service; 22 | 23 | @Mock 24 | RestTemplate restTemplate; 25 | 26 | @Before 27 | public void setup() { 28 | MockitoAnnotations.initMocks(this); 29 | 30 | this.mockMvc = MockMvcBuilders.standaloneSetup(service).build(); 31 | } 32 | 33 | } 34 | --------------------------------------------------------------------------------