diff --git a/watsonml-java-proxy/.gitignore b/watsonml-java-proxy/.gitignore
new file mode 100644
index 0000000..5bc4cc6
--- /dev/null
+++ b/watsonml-java-proxy/.gitignore
@@ -0,0 +1,40 @@
+*#
+*.iml
+*.ipr
+*.iws
+*.jar
+*.sw?
+*~
+.#*
+.*.md.html
+.DS_Store
+.classpath
+.factorypath
+.gradle
+.idea
+.metadata
+.project
+.recommenders
+.settings
+.springBeans
+/build
+/code
+MANIFEST.MF
+_site/
+activemq-data
+bin
+build
+build.log
+dependency-reduced-pom.xml
+dump.rdb
+interpolated*.xml
+lib/
+manifest.yml
+overridedb.*
+settings.xml
+target
+transaction-logs
+.flattened-pom.xml
+secrets.yml
+.gradletasknamecache
+.sts4-cache
diff --git a/watsonml-java-proxy/pom.xml b/watsonml-java-proxy/pom.xml
new file mode 100644
index 0000000..6e01fbd
--- /dev/null
+++ b/watsonml-java-proxy/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+
+ com.ibm
+ watsonml
+ 1.0-SNAPSHOT
+
+ war
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.10.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+ com.zaxxer
+ HikariCP
+
+
+ org.hibernate
+ hibernate-validator
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.7.0
+ compile
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.7.0
+ compile
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.data
+ spring-data-couchbase
+ 3.0.7.RELEASE
+
+
+
+
+ com.cloudant
+ cloudant-client
+ 2.12.0
+
+
+
+
+ com.ibm.mobilefirstplatform.serversdk.java
+ push
+ 1.1.0
+
+
+
+
+
+ 1.8
+
+
+
+
+
+ watsonml
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-releases
+ https://repo.spring.io/libs-release
+
+
+
+
+ spring-releases
+ https://repo.spring.io/libs-release
+
+
+
+
\ No newline at end of file
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/Application.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/Application.java
new file mode 100644
index 0000000..ec43b8c
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/Application.java
@@ -0,0 +1,33 @@
+package com.ibm.watsonml;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.AsyncRestTemplate;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootApplication
+public class Application {
+
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class,args);
+ }
+
+
+ /**
+ * Rest template configuration
+ * @param builder
+ * @return
+ */
+ @Bean
+ public RestTemplate restTemplate(RestTemplateBuilder builder) {
+ return builder.build();
+ }
+
+ @Bean
+ AsyncRestTemplate asyncRestTemplate() {
+ return new AsyncRestTemplate();
+ }
+}
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/AvatarController.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/AvatarController.java
new file mode 100644
index 0000000..34de712
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/AvatarController.java
@@ -0,0 +1,42 @@
+package com.ibm.watsonml.controller;
+
+
+import com.ibm.watsonml.service.ScoreEntryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.CompletableFuture;
+
+@RestController
+@RequestMapping("/avatar")
+public class AvatarController {
+
+
+ Logger log = LoggerFactory.getLogger(this.getClass().getName());
+
+
+ @Autowired
+ private ScoreEntryService scoreEntryService;
+
+
+ @GetMapping("/leaderboardAvatar/{id}")
+ public CompletableFuture getLeaderBoardAvatar(@PathVariable String id){
+
+ CompletableFuture media = new CompletableFuture<>();
+ try {
+ media = scoreEntryService.getLeaderBoardAvatar(id);
+ } catch (InterruptedException e) {
+ log.error("Not able to get image bytes");
+ media.completeExceptionally(e);
+ }
+
+ return media;
+ }
+
+
+}
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/ScoreEntryController.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/ScoreEntryController.java
new file mode 100644
index 0000000..eb097ed
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/controller/ScoreEntryController.java
@@ -0,0 +1,93 @@
+package com.ibm.watsonml.controller;
+
+
+import com.ibm.watsonml.model.ScoreEntry;
+import com.ibm.watsonml.model.UserCount;
+import com.ibm.watsonml.service.ScoreEntryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@RestController
+@RequestMapping("/watsonml")
+public class ScoreEntryController {
+
+ Logger log = LoggerFactory.getLogger(this.getClass().getName());
+
+
+ @Autowired
+ private ScoreEntryService scoreEntryService;
+
+
+ @PostMapping("/entries")
+ public CompletableFuture saveUserData(HttpServletRequest request, @RequestBody ScoreEntry scoreEntry) {
+ log.debug("REST request to save user score");
+ CompletableFuture entries = new CompletableFuture<>();
+ try {
+ entries.complete(scoreEntryService.saveUserScore(scoreEntry).get());
+ } catch (Exception e) {
+ log.error("Error while making external API call {}", e);
+ entries.completeExceptionally(e);
+ }
+ return entries;
+ }
+
+ @PutMapping("/entries/{identifier}")
+ public CompletableFuture updateUserData(@RequestBody ScoreEntry scoreEntry,@PathVariable String identifier) {
+ log.debug("REST request to update user score");
+ CompletableFuture entries = new CompletableFuture<>();
+ try {
+ entries.complete(scoreEntryService.updateUserScore(scoreEntry).get());
+ } catch (Exception e) {
+ log.error("Error while making external API call {}", e);
+ entries.completeExceptionally(e);
+ }
+ return entries;
+ }
+
+
+ @GetMapping("/leaderboard")
+ public CompletableFuture> getLeaderBoard() {
+ log.debug("REST request to get leaderboard");
+ CompletableFuture> entries = new CompletableFuture<>();
+ try {
+ entries.complete(scoreEntryService.getLeaderBoard().get());
+ } catch (Exception e) {
+ log.error("Error while making external API call {}", e);
+ entries.completeExceptionally(e);
+ }
+ return entries;
+ }
+
+ @GetMapping("/leaderboard/{id}")
+ public CompletableFuture> getLeaderBoardForUser(@PathVariable String id) {
+ log.debug("REST request to get leaderboard");
+ CompletableFuture> entries = new CompletableFuture<>();
+ try {
+ entries.complete(scoreEntryService.getLeaderBoard(id).get());
+ } catch (Exception e) {
+ log.error("Error while making external API call {}", e);
+ entries.completeExceptionally(e);
+ }
+ return entries;
+ }
+
+
+ @GetMapping("/user/counts")
+ public CompletableFuture getTotaUsersCount() {
+ log.debug("REST request to get leaderboard");
+ CompletableFuture entries = new CompletableFuture<>();
+ try {
+ entries.complete(scoreEntryService.getUserCount().get());
+ } catch (Exception e) {
+ log.error("Error while making external API call {}", e);
+ entries.completeExceptionally(e);
+ }
+ return entries;
+ }
+}
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/IdentifiedObjects.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/IdentifiedObjects.java
new file mode 100644
index 0000000..2d66c5e
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/IdentifiedObjects.java
@@ -0,0 +1,27 @@
+package com.ibm.watsonml.model;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class IdentifiedObjects implements Serializable{
+
+ private String name;
+ private BigDecimal timestamp;
+
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public BigDecimal getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(BigDecimal timestamp) {
+ this.timestamp = timestamp;
+ }
+}
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/ScoreEntry.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/ScoreEntry.java
new file mode 100644
index 0000000..bb38efc
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/model/ScoreEntry.java
@@ -0,0 +1,81 @@
+package com.ibm.watsonml.model;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class ScoreEntry implements Serializable{
+
+ private String id;
+ private String username;
+ private BigDecimal startDate;
+ private BigDecimal finishDate;
+ private String deviceIdentifier;
+ private String avatarImage;
+ List objects;
+ Double totalTime;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public BigDecimal getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(BigDecimal startDate) {
+ this.startDate = startDate;
+ }
+
+ public BigDecimal getFinishDate() {
+ return finishDate;
+ }
+
+ public void setFinishDate(BigDecimal finishDate) {
+ this.finishDate = finishDate;
+ }
+
+ public String getDeviceIdentifier() {
+ return deviceIdentifier;
+ }
+
+ public void setDeviceIdentifier(String deviceIdentifier) {
+ this.deviceIdentifier = deviceIdentifier;
+ }
+
+ public String getAvatarImage() {
+ return avatarImage;
+ }
+
+ public void setAvatarImage(String avatarImage) {
+ this.avatarImage = avatarImage;
+ }
+
+ public List getObjects() {
+ return objects;
+ }
+
+ public void setObjects(List objects) {
+ this.objects = objects;
+ }
+
+ public Double getTotalTime() {
+ return totalTime;
+ }
+
+ public void setTotalTime(Double totalTime) {
+ this.totalTime = totalTime;
+ }
+}
diff --git a/watsonml-java-proxy/src/main/java/com/ibm/watsonml/service/ScoreEntryService.java b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/service/ScoreEntryService.java
new file mode 100644
index 0000000..dcc2d1b
--- /dev/null
+++ b/watsonml-java-proxy/src/main/java/com/ibm/watsonml/service/ScoreEntryService.java
@@ -0,0 +1,173 @@
+package com.ibm.watsonml.service;
+
+
+import com.ibm.watsonml.model.ScoreEntry;
+import com.ibm.watsonml.model.UserCount;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@Service
+@Transactional
+public class ScoreEntryService {
+
+ private final Logger log = LoggerFactory.getLogger(ScoreEntryService.class);
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Value("${application.kituraUrl.save.score}")
+ private String saveScoreEntryURL;
+
+ @Value("${application.kituraUrl.update.score}")
+ private String updateScoreEntryURL;
+
+ @Value("${application.kituraUrl.get.leaderboard}")
+ private String getLeaderBoardURL;
+
+ @Value("${application.kituraUrl.get.leaderboard.avatar}")
+ private String getLeaderBoardAvatarURL;
+
+ @Value("${application.kituraUrl.get.user.count}")
+ private String getUserCountURL;
+
+ @Autowired
+ HttpServletRequest request;
+
+ private HttpHeaders getHeader() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Authorization", request.getHeader("Authorization"));
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ return headers;
+ }
+
+
+ /**
+ * Method to asynchronously call external API
+ * @return
+ * @throws InterruptedException
+ */
+ @Async
+ public CompletableFuture saveUserScore(ScoreEntry scoreEntry) throws InterruptedException{
+ log.info("Saving Score entry to database.");
+
+ HttpHeaders headers = getHeader();
+ ResponseEntity response=restTemplate.exchange
+ (saveScoreEntryURL, HttpMethod.POST, new HttpEntity<>(scoreEntry, headers), ScoreEntry.class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(response.getBody());
+ }
+
+
+ /**
+ * Method to asynchronously call external API
+ * @return
+ * @throws InterruptedException
+ */
+ @Async
+ public CompletableFuture updateUserScore(ScoreEntry scoreEntry) throws InterruptedException{
+ log.info("Updating Score entry to database.");
+
+ String updateURL = new StringBuilder(saveScoreEntryURL).append("/").append(scoreEntry.getId()).toString();
+
+ HttpHeaders headers = getHeader();
+ ResponseEntity response=restTemplate.exchange
+ (updateURL, HttpMethod.PUT, new HttpEntity<>(scoreEntry, headers), ScoreEntry.class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(response.getBody());
+ }
+
+ /**
+ * Method to asynchronously call external API
+ * @return
+ * @throws InterruptedException
+ */
+ @Async
+ public CompletableFuture> getLeaderBoard() throws InterruptedException{
+ log.info("Getting leaderboard data.");
+
+ HttpHeaders headers = getHeader();
+ ResponseEntity response=restTemplate.exchange
+ (getLeaderBoardURL, HttpMethod.GET, new HttpEntity<>( headers), ScoreEntry[].class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(Arrays.asList(response.getBody()));
+ }
+
+ /**
+ * Method to asynchronously call external API
+ * @return
+ * @throws InterruptedException
+ */
+ @Async
+ public CompletableFuture> getLeaderBoard(String id) throws InterruptedException{
+ log.info("Getting leaderboard data for top 10 users.");
+
+ HttpHeaders headers = getHeader();
+
+ StringBuilder topTenLeaderBoardURl = new StringBuilder(getLeaderBoardURL).append("/").append(id);
+ ResponseEntity response=restTemplate.exchange
+ (topTenLeaderBoardURl.toString(), HttpMethod.GET, new HttpEntity<>( headers), ScoreEntry[].class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(Arrays.asList(response.getBody()));
+ }
+
+ /**
+ * Method to asynchronously call external API
+ * @return
+ * @throws InterruptedException
+ */
+ @Async
+ public CompletableFuture getLeaderBoardAvatar(String id) throws InterruptedException{
+ log.info("Getting leaderboard avatar.");
+ StringBuilder avatarUrl = new StringBuilder(getLeaderBoardAvatarURL).append("/").append(id).append(".png");
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setCacheControl(CacheControl.noCache().getHeaderValue());
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ ResponseEntity response=restTemplate.exchange
+ (avatarUrl.toString(), HttpMethod.GET, new HttpEntity<>( headers), byte[].class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(response.getBody());
+ }
+
+
+ /**
+ * Get count of users and users completing the game
+ * @return
+ * @throws InterruptedException
+ */
+ public CompletableFuture getUserCount() throws InterruptedException {
+ log.info("Getting User count.");
+
+ HttpHeaders headers = getHeader();
+ ResponseEntity response=restTemplate.exchange
+ (getUserCountURL, HttpMethod.GET, new HttpEntity<>( headers), UserCount.class);
+
+ // delay of 1s
+ Thread.sleep(1000L);
+ return CompletableFuture.completedFuture(response.getBody());
+
+ }
+}
diff --git a/watsonml-java-proxy/src/main/resources/application.yml b/watsonml-java-proxy/src/main/resources/application.yml
new file mode 100644
index 0000000..11b3861
--- /dev/null
+++ b/watsonml-java-proxy/src/main/resources/application.yml
@@ -0,0 +1,21 @@
+logging:
+ level:
+ ROOT: DEBUG
+ com.comcast.helloworld: DEBUG
+
+spring:
+ application:
+ name: WatsonML
+ jackson:
+ serialization.indent_output: false
+
+server:
+ port: 8080
+
+application:
+ kituraUrl:
+ save.score: https://rainbow-scavenger-viz-rec.mybluemix.net/watsonml/entries
+ update.score: https://rainbow-scavenger-viz-rec.mybluemix.net/watsonml/entries
+ get.leaderboard: https://rainbow-scavenger-viz-rec.mybluemix.net/watsonml/leaderboard
+ get.leaderboard.avatar: https://rainbow-scavenger-viz-rec.mybluemix.net/avatar/leaderboardAvatar
+ get.user.count: https://rainbow-scavenger-viz-rec.mybluemix.net/watsonml/user/counts
\ No newline at end of file