Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First Drop of Monty Hall REST API Spring MVC web app

  • Loading branch information...
commit b99af004bbf6ee81b2bf97470f9b39806fbb1578 0 parents
@acolyer authored
Showing with 818 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +81 −0 Sample Interaction.txt
  3. +50 −0 build.gradle
  4. +20 −0 cargo.gradle
  5. +23 −0 src/main/java/org/springsource/samples/montyhall/config/WebConfig.java
  6. +163 −0 src/main/java/org/springsource/samples/montyhall/controller/GameController.java
  7. +53 −0 src/main/java/org/springsource/samples/montyhall/domain/Door.java
  8. +5 −0 src/main/java/org/springsource/samples/montyhall/domain/DoorStatus.java
  9. +99 −0 src/main/java/org/springsource/samples/montyhall/domain/Game.java
  10. +15 −0 src/main/java/org/springsource/samples/montyhall/domain/GameEvent.java
  11. +8 −0 src/main/java/org/springsource/samples/montyhall/domain/GameStatus.java
  12. +6 −0 src/main/java/org/springsource/samples/montyhall/domain/Prize.java
  13. +11 −0 src/main/java/org/springsource/samples/montyhall/repository/GameRepository.java
  14. +32 −0 src/main/java/org/springsource/samples/montyhall/repository/InMemoryGameRepository.java
  15. +14 −0 src/main/java/org/springsource/samples/montyhall/resource/ClickStreamResource.java
  16. +17 −0 src/main/java/org/springsource/samples/montyhall/resource/DoorResource.java
  17. +30 −0 src/main/java/org/springsource/samples/montyhall/resource/DoorResourceAssembler.java
  18. +14 −0 src/main/java/org/springsource/samples/montyhall/resource/DoorStatusResource.java
  19. +11 −0 src/main/java/org/springsource/samples/montyhall/resource/DoorsResource.java
  20. +35 −0 src/main/java/org/springsource/samples/montyhall/resource/DoorsResourceAssembler.java
  21. +12 −0 src/main/java/org/springsource/samples/montyhall/resource/GameResource.java
  22. +26 −0 src/main/java/org/springsource/samples/montyhall/resource/GameResourceAssembler.java
  23. +14 −0 src/main/java/org/springsource/samples/montyhall/resource/HistoryResource.java
  24. +25 −0 src/main/java/org/springsource/samples/montyhall/resource/HistoryResourceAssembler.java
  25. +17 −0 src/main/java/org/springsource/samples/montyhall/service/ClickStreamService.java
  26. +33 −0 src/main/java/org/springsource/samples/montyhall/web/MontyHallWebAppInitializer.java
4 .gitignore
@@ -0,0 +1,4 @@
+build
+*~
+gradle.properties
+.gradle/
81 Sample Interaction.txt
@@ -0,0 +1,81 @@
+$>curl -X POST -I http://localhost:8080/monty-hall/games
+
+HTTP/1.1 201 Created
+Server: Apache-Coyote/1.1
+Location: http://localhost:8080/monty-hall/games/2863629425905948275
+Content-Length: 0
+Date: Mon, 01 Oct 2012 17:58:59 GMT
+
+$>curl http://localhost:8080/monty-hall/games/2863629425905948275
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275"},
+ {"rel":"doors","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors"},
+ {"rel":"history","href":"http://localhost:8080/monty-hall/games/2863629425905948275/history"}],
+ "status":"AWAITING_INITIAL_SELECTION"}
+
+$>url http://localhost:8080/monty-hall/games/2863629425905948275/doors
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors"}],
+ "doors":[
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/1"}],
+ "status":"CLOSED","content":"UNKNOWN"},
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/2"}],
+ "status":"CLOSED","content":"UNKNOWN"},
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/3"}],
+ "status":"CLOSED","content":"UNKNOWN"}]
+}
+
+$>curl -X PUT -H "Content-Type: application/json" -d "{\"status\":\"SELECTED\"}" http://localhost:8080/monty-hall/games/2863629425905948275/doors/1
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/1"}],
+ "status":"SELECTED","content":"UNKNOWN"}
+
+$>curl http://localhost:8080/monty-hall/games/2863629425905948275/history
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/history"}],
+ "events":["SELECTED_DOOR_ONE","REVEALED_DOOR_THREE"]}
+
+$>curl http://localhost:8080/monty-hall/games/2863629425905948275/doors
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors"}],
+ "doors":[
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/1"}],
+ "status":"SELECTED","content":"UNKNOWN"},
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/2"}],
+ "status":"CLOSED","content":"UNKNOWN"},
+ {"links":[{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/3"}],
+ "status":"OPENED","content":"SMALL_FURRY_ANIMAL"}]
+}
+
+$>curl -X PUT -H "Content-Type: application/json" -d "{\"status\":\"OPENED\"}" http://localhost:8080/monty-hall/games/2863629425905948275/doors/2
+
+{"links":[
+ {"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors/2"}],
+ "status":"OPENED","content":"SMALL_FURRY_ANIMAL"}
+
+$>curl http://localhost:8080/monty-hall/games/2863629425905948275
+
+{"links":[
+ {"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275"},
+ {"rel":"doors","href":"http://localhost:8080/monty-hall/games/2863629425905948275/doors"},
+ {"rel":"history","href":"http://localhost:8080/monty-hall/games/2863629425905948275/history"}],
+ "status":"LOST"}
+
+$>curl http://localhost:8080/monty-hall/games/2863629425905948275/history
+
+{"links":
+ [{"rel":"self","href":"http://localhost:8080/monty-hall/games/2863629425905948275/history"}],
+ "events":["SELECTED_DOOR_ONE","REVEALED_DOOR_THREE", "OPENED_DOOR_TWO","LOST"]}
+
+$>curl -X POST -i -H "Content-Type: application/json" -d "{\"data\":\"OPENED AND SHUT\"}" http://localhost:8080/monty-hall/games/3144502268854998881/clicks
+
+HTTP/1.1 201 Created
+Server: Apache-Coyote/1.1
+Content-Length: 0
+Date: Mon, 01 Oct 2012 18:37:39 GMT
+
50 build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'cloudfoundry'
+
+apply from: 'cargo.gradle'
+
+repositories {
+ mavenCentral()
+
+ maven { url 'http://repo.springsource.org/libs-snapshot' }
+ maven { url 'http://repo.springsource.org/libs-milestone'}
+}
+
+dependencies {
+ compile 'org.springframework:spring-context:3.1.0.RELEASE'
+ compile 'org.springframework:spring-webmvc:3.1.0.RELEASE'
+ compile 'org.springframework.hateoas:spring-hateoas:0.3.0.BUILD-SNAPSHOT'
+ compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.9'
+ compile 'cglib:cglib-nodep:2.2'
+ providedCompile 'org.mortbay.jetty:servlet-api:3.0.20100224'
+ testCompile group: 'junit', name: 'junit', version: '4.+'
+}
+
+cloudfoundry {
+ username = "$cloudfoundryUserName"
+ password = "$cloudfoundryPassword"
+ application = "$cloudfoundryAppName"
+ target = "$cloudfoundryTarget"
+ framework = "spring"
+ file = new File('build/libs/monty-hall.war')
+}
+
+buildscript {
+
+ repositories {
+ add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+ name = 'GitHub'
+ addArtifactPattern 'http://cloud.github.com/downloads/[organisation]/[module]/[module]-[revision].[ext]'
+ }
+ mavenCentral()
+ maven { url 'http://maven.springframework.org/milestone' }
+
+ }
+
+ dependencies {
+ classpath 'bmuschko:gradle-cargo-plugin:0.5.2'
+ classpath 'org.gradle.api.plugins:gradle-cf-plugin:0.2.0'
+ }
+}
+
20 cargo.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'cargo'
+
+dependencies {
+ def cargoVersion = '1.1.3'
+ cargo "org.codehaus.cargo:cargo-core-uberjar:$cargoVersion",
+ "org.codehaus.cargo:cargo-ant:$cargoVersion"
+}
+
+cargo {
+ containerId = 'tomcat7x'
+ port = 8080
+ deployable {
+ context = name
+ }
+
+ local {
+ homeDir = file('/Users/adrian/ext/apache-tomcat-7.0.30')
+ log = file('build/tomcat.log')
+ }
+}
23 src/main/java/org/springsource/samples/montyhall/config/WebConfig.java
@@ -0,0 +1,23 @@
+package org.springsource.samples.montyhall.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
+
+@Configuration
+@EnableWebMvc
+@ComponentScan("org.springsource.samples.montyhall")
+public class WebConfig {
+
+ @Bean
+ public ViewResolver viewResolver() {
+ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
+ viewResolver.setPrefix("/WEB-INF/jsp/");
+ viewResolver.setSuffix(".jsp");
+ return viewResolver;
+ }
+
+}
163 src/main/java/org/springsource/samples/montyhall/controller/GameController.java
@@ -0,0 +1,163 @@
+package org.springsource.samples.montyhall.controller;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.HttpStatus;
+
+import org.springsource.samples.montyhall.domain.Game;
+import org.springsource.samples.montyhall.domain.Door;
+import org.springsource.samples.montyhall.domain.DoorStatus;
+import org.springsource.samples.montyhall.resource.GameResource;
+import org.springsource.samples.montyhall.resource.GameResourceAssembler;
+import org.springsource.samples.montyhall.resource.DoorResource;
+import org.springsource.samples.montyhall.resource.DoorStatusResource;
+import org.springsource.samples.montyhall.resource.DoorResourceAssembler;
+import org.springsource.samples.montyhall.resource.DoorsResource;
+import org.springsource.samples.montyhall.resource.DoorsResourceAssembler;
+import org.springsource.samples.montyhall.resource.HistoryResource;
+import org.springsource.samples.montyhall.resource.HistoryResourceAssembler;
+import org.springsource.samples.montyhall.repository.GameRepository;
+import org.springsource.samples.montyhall.resource.ClickStreamResource;
+import org.springsource.samples.montyhall.service.ClickStreamService;
+
+@Controller
+@RequestMapping("/games")
+public class GameController {
+ // Keeping things simple with a single controller for the
+ // GameResource aggregate root.
+
+ private final GameRepository repository;
+ private final GameResourceAssembler gameResourceAssembler;
+ private final DoorResourceAssembler doorResourceAssembler;
+ private final DoorsResourceAssembler doorsResourceAssembler;
+ private final HistoryResourceAssembler historyResourceAssembler;
+ private final ClickStreamService clickStreamService;
+
+ @Autowired
+ public GameController(GameRepository repo,
+ GameResourceAssembler assembler,
+ DoorResourceAssembler doorAssembler,
+ DoorsResourceAssembler doorsAssembler,
+ HistoryResourceAssembler historyAssembler,
+ ClickStreamService clicks) {
+ this.repository = repo;
+ this.gameResourceAssembler = assembler;
+ this.doorResourceAssembler = doorAssembler;
+ this.doorsResourceAssembler = doorsAssembler;
+ this.historyResourceAssembler = historyAssembler;
+ this.clickStreamService = clicks;
+ }
+
+ @RequestMapping(method=RequestMethod.POST)
+ public HttpEntity createGame() {
+ Game game = new Game();
+ this.repository.storeGame(game);
+ HttpHeaders headers = new HttpHeaders();
+ headers.setLocation(linkTo(GameController.class).slash(game).toUri());
+ return new ResponseEntity<GameResource>(headers, HttpStatus.CREATED);
+ }
+
+ @RequestMapping(value="/{id}",method=RequestMethod.GET)
+ public HttpEntity<GameResource> show(@PathVariable Long id) {
+ Game game = this.repository.findById(id);
+ if (game == null) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<GameResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ GameResource resource = this.gameResourceAssembler.toResource(game);
+ return new ResponseEntity<GameResource>(resource, HttpStatus.OK);
+ }
+
+ @RequestMapping(value="/{id}/doors", method=RequestMethod.GET)
+ public HttpEntity<DoorsResource> showDoors(@PathVariable Long id) {
+ Game game = this.repository.findById(id);
+ if (game == null) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<DoorsResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ DoorsResource resource = this.doorsResourceAssembler.toResource(game);
+ return new ResponseEntity<DoorsResource>(resource, HttpStatus.OK);
+ }
+
+ @RequestMapping(value="/{gameId}/doors/{doorId}", method=RequestMethod.GET)
+ public HttpEntity<DoorResource> showDoor(@PathVariable Long gameId, @PathVariable Long doorId) {
+ Game game = this.repository.findById(gameId);
+ if (game == null || doorId < 1 || doorId > 3) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<DoorResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ Door door = game.getDoors().get((int)(doorId - 1));
+ DoorResource resource = this.doorResourceAssembler.toResource(game,door);
+ return new ResponseEntity<DoorResource>(resource, HttpStatus.OK);
+ }
+
+ @RequestMapping(value="/{gameId}/doors/{doorId}", method=RequestMethod.PUT)
+ public HttpEntity<DoorResource> updateDoor(@PathVariable Long gameId,
+ @PathVariable Long doorId,
+ @RequestBody DoorStatusResource doorResource) {
+ Game game = this.repository.findById(gameId);
+ if (game == null || doorId < 1 || doorId > 3) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<DoorResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ Door door = game.getDoors().get((int)(doorId - 1));
+
+ try {
+ if (doorResource.status == DoorStatus.SELECTED) {
+ game.select(door);
+ } else if (doorResource.status == DoorStatus.OPENED) {
+ game.open(door);
+ }
+ }
+ catch (IllegalStateException ex) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<DoorResource>(headers, HttpStatus.CONFLICT);
+ }
+
+ DoorResource resource = this.doorResourceAssembler.toResource(game,door);
+ return new ResponseEntity<DoorResource>(resource, HttpStatus.OK);
+ }
+
+ @RequestMapping(value="/{id}/history", method=RequestMethod.GET)
+ public HttpEntity<HistoryResource> showHistory(@PathVariable Long id) {
+ Game game = this.repository.findById(id);
+ if (game == null) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<HistoryResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ HistoryResource resource = this.historyResourceAssembler.toResource(game);
+ return new ResponseEntity<HistoryResource>(resource, HttpStatus.OK);
+ }
+
+
+ /** and a handler method for the click stream data */
+ @RequestMapping(value="/{id}/clicks", method=RequestMethod.POST)
+ public HttpEntity recordClickStreamData(@PathVariable Long id,
+ @RequestBody ClickStreamResource clicks) {
+ Game game = this.repository.findById(id);
+ if (game == null) {
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<GameResource>(headers, HttpStatus.NOT_FOUND);
+ }
+
+ this.clickStreamService.recordClickStream(game,clicks);
+
+ HttpHeaders headers = new HttpHeaders();
+ return new ResponseEntity<GameResource>(headers, HttpStatus.CREATED);
+ }
+
+}
53 src/main/java/org/springsource/samples/montyhall/domain/Door.java
@@ -0,0 +1,53 @@
+package org.springsource.samples.montyhall.domain;
+
+import org.springframework.hateoas.Identifiable;
+
+public class Door implements Identifiable<Integer> {
+ private final Prize prize;
+ private final int id;
+ private DoorStatus status = DoorStatus.CLOSED;
+
+ public Door(int id, Prize prize) {
+ this.id = id;
+ this.prize = prize;
+ }
+
+ public Integer getId() {
+ return this.id;
+ }
+
+ public Prize getPrize() {
+ return this.prize;
+ }
+
+ public DoorStatus getStatus() {
+ return this.status;
+ }
+
+ public void setStatus(DoorStatus status) {
+ boolean illegalTransitionAttempted = false;
+ switch (status) {
+ case CLOSED :
+ if (this.status != DoorStatus.CLOSED) {
+ illegalTransitionAttempted = true;
+ }
+ break;
+ case SELECTED :
+ if (this.status == DoorStatus.OPENED) {
+ illegalTransitionAttempted = true;
+ }
+ break;
+ case OPENED :
+ break;
+ }
+ if (illegalTransitionAttempted) {
+ throw new IllegalStateException("Cannot transition to " + status + " from " + this.status);
+ }
+ this.status = status;
+ }
+
+ public void reveal() {
+ this.status = DoorStatus.OPENED;
+ }
+
+}
5 src/main/java/org/springsource/samples/montyhall/domain/DoorStatus.java
@@ -0,0 +1,5 @@
+package org.springsource.samples.montyhall.domain;
+
+public enum DoorStatus {
+ CLOSED, SELECTED, OPENED
+}
99 src/main/java/org/springsource/samples/montyhall/domain/Game.java
@@ -0,0 +1,99 @@
+package org.springsource.samples.montyhall.domain;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.springframework.hateoas.Identifiable;
+
+public class Game implements Identifiable<Long> {
+
+ private Long id;
+ private GameStatus status = GameStatus.AWAITING_INITIAL_SELECTION;
+ private Door[] doors = new Door[3];
+ private List<GameEvent> history = new ArrayList<GameEvent>();
+
+
+ public Game() {
+ int doorWithJuergen = new Random().nextInt(3);
+ doors[0] = (doorWithJuergen == 0) ? new Door(1,Prize.JUERGEN) : new Door(1,Prize.SMALL_FURRY_ANIMAL);
+ doors[1] = (doorWithJuergen == 1) ? new Door(2,Prize.JUERGEN) : new Door(2,Prize.SMALL_FURRY_ANIMAL);
+ doors[2] = (doorWithJuergen == 2) ? new Door(3,Prize.JUERGEN) : new Door(3,Prize.SMALL_FURRY_ANIMAL);
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public GameStatus getStatus() {
+ return this.status;
+ }
+
+ public List<Door> getDoors() {
+ List<Door> doorList = new ArrayList<Door>(3);
+ doorList.add(this.doors[0]);
+ doorList.add(this.doors[1]);
+ doorList.add(this.doors[2]);
+ return doorList;
+ }
+
+ public List<GameEvent> getHistory() {
+ return this.history;
+ }
+
+
+ /** Select a door, and return the door opened by the host */
+ public Door select(Door door) {
+ if (this.status != GameStatus.AWAITING_INITIAL_SELECTION) {
+ throw new IllegalStateException("Can't select a door from state " + this.status);
+ }
+
+ Door toSelect = this.doors[door.getId() - 1];
+ toSelect.setStatus(DoorStatus.SELECTED);
+ GameEvent selectEvent = door.getId() == 1 ? GameEvent.SELECTED_DOOR_ONE
+ : (door.getId() == 2 ? GameEvent.SELECTED_DOOR_TWO : GameEvent.SELECTED_DOOR_THREE);
+ this.history.add(selectEvent);
+
+ int doorToReveal = new Random().nextInt(3);
+ while ( (this.doors[doorToReveal] == toSelect) || (this.doors[doorToReveal].getPrize() == Prize.JUERGEN) ) {
+ doorToReveal = new Random().nextInt(3);
+ }
+ Door toReveal = this.doors[doorToReveal];
+ toReveal.reveal();
+ GameEvent revealEvent = doorToReveal == 0 ? GameEvent.REVEALED_DOOR_ONE
+ : (doorToReveal == 1 ? GameEvent.REVEALED_DOOR_TWO : GameEvent.REVEALED_DOOR_THREE);
+ this.history.add(revealEvent);
+ this.status = GameStatus.AWAITING_FINAL_SELECTION;
+ return toReveal;
+ }
+
+ public GameStatus open(Door door) {
+ if (this.status != GameStatus.AWAITING_FINAL_SELECTION) {
+ throw new IllegalStateException("Can't open a door from state " + this.status);
+ }
+ Door toOpen = this.doors[door.getId() -1];
+ toOpen.setStatus(DoorStatus.OPENED);
+ Prize prize = toOpen.getPrize();
+ if (prize == Prize.JUERGEN) {
+ this.status = GameStatus.WON;
+ }
+ else {
+ this.status = GameStatus.LOST;
+ }
+
+ GameEvent openEvent = (door.getId() == 1 ? GameEvent.OPENED_DOOR_ONE
+ : (door.getId() == 2 ? GameEvent.OPENED_DOOR_TWO : GameEvent.OPENED_DOOR_THREE));
+ this.history.add(openEvent);
+ if (this.status == GameStatus.WON) {
+ this.history.add(GameEvent.WON);
+ } else {
+ this.history.add(GameEvent.LOST);
+ }
+
+ return this.status;
+ }
+}
15 src/main/java/org/springsource/samples/montyhall/domain/GameEvent.java
@@ -0,0 +1,15 @@
+package org.springsource.samples.montyhall.domain;
+
+public enum GameEvent {
+ SELECTED_DOOR_ONE,
+ SELECTED_DOOR_TWO,
+ SELECTED_DOOR_THREE,
+ REVEALED_DOOR_ONE,
+ REVEALED_DOOR_TWO,
+ REVEALED_DOOR_THREE,
+ OPENED_DOOR_ONE,
+ OPENED_DOOR_TWO,
+ OPENED_DOOR_THREE,
+ WON,
+ LOST
+}
8 src/main/java/org/springsource/samples/montyhall/domain/GameStatus.java
@@ -0,0 +1,8 @@
+package org.springsource.samples.montyhall.domain;
+
+public enum GameStatus {
+ AWAITING_INITIAL_SELECTION,
+ AWAITING_FINAL_SELECTION,
+ WON,
+ LOST
+}
6 src/main/java/org/springsource/samples/montyhall/domain/Prize.java
@@ -0,0 +1,6 @@
+package org.springsource.samples.montyhall.domain;
+
+public enum Prize {
+ SMALL_FURRY_ANIMAL,
+ JUERGEN
+}
11 src/main/java/org/springsource/samples/montyhall/repository/GameRepository.java
@@ -0,0 +1,11 @@
+package org.springsource.samples.montyhall.repository;
+
+import org.springsource.samples.montyhall.domain.Game;
+
+public interface GameRepository {
+
+ Game findById(Long gameId);
+
+ void storeGame(Game game);
+
+}
32 src/main/java/org/springsource/samples/montyhall/repository/InMemoryGameRepository.java
@@ -0,0 +1,32 @@
+package org.springsource.samples.montyhall.repository;
+
+import org.springsource.samples.montyhall.domain.Game;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Random;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class InMemoryGameRepository implements GameRepository {
+
+ private Map<Long,Game> games = Collections.synchronizedMap(new HashMap<Long,Game>());
+
+ public Game findById(Long id) {
+ return this.games.get(id);
+ }
+
+ public void storeGame(Game game) {
+ if (game.getId() == null) {
+ synchronized(games) {
+ Long newId = new Random().nextLong();
+ while ((newId <= 0) || this.games.containsKey(newId)) {
+ newId = new Random().nextLong();
+ }
+ game.setId(newId);
+ games.put(newId,game);
+ }
+ }
+ }
+}
+
14 src/main/java/org/springsource/samples/montyhall/resource/ClickStreamResource.java
@@ -0,0 +1,14 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import org.springsource.samples.montyhall.domain.DoorStatus;
+
+/** Used when mapping incoming POST requests with Click Stream data */
+public class ClickStreamResource {
+
+ @JsonProperty("data")
+ public String data;
+
+}
17 src/main/java/org/springsource/samples/montyhall/resource/DoorResource.java
@@ -0,0 +1,17 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import org.springsource.samples.montyhall.domain.DoorStatus;
+
+
+public class DoorResource extends ResourceSupport {
+
+ @JsonProperty("status")
+ DoorStatus status;
+
+ @JsonProperty("content")
+ String content;
+
+}
30 src/main/java/org/springsource/samples/montyhall/resource/DoorResourceAssembler.java
@@ -0,0 +1,30 @@
+package org.springsource.samples.montyhall.resource;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
+import org.springframework.stereotype.Component;
+import org.springsource.samples.montyhall.controller.GameController;
+import org.springsource.samples.montyhall.domain.Game;
+import org.springsource.samples.montyhall.domain.Door;
+import org.springsource.samples.montyhall.domain.DoorStatus;
+import org.springsource.samples.montyhall.domain.Prize;
+
+@Component
+public class DoorResourceAssembler {
+
+ public DoorResourceAssembler() {
+ }
+
+ public DoorResource toResource(Game game, Door door) {
+ DoorResource resource = new DoorResource();
+ resource.status = door.getStatus();
+ if (door.getStatus() == DoorStatus.OPENED) {
+ resource.content = door.getPrize().toString();
+ } else {
+ resource.content = "UNKNOWN";
+ }
+ resource.add(linkTo(GameController.class).slash(game).slash("doors").slash(door).withSelfRel());
+ return resource;
+ }
+}
14 src/main/java/org/springsource/samples/montyhall/resource/DoorStatusResource.java
@@ -0,0 +1,14 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import org.springsource.samples.montyhall.domain.DoorStatus;
+
+/** Used when mapping incoming PUT / PATCH requests with partial info */
+public class DoorStatusResource {
+
+ @JsonProperty("status")
+ public DoorStatus status;
+
+}
11 src/main/java/org/springsource/samples/montyhall/resource/DoorsResource.java
@@ -0,0 +1,11 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+public class DoorsResource extends ResourceSupport {
+
+ @JsonProperty("doors")
+ DoorResource[] doors = new DoorResource[3];
+
+}
35 src/main/java/org/springsource/samples/montyhall/resource/DoorsResourceAssembler.java
@@ -0,0 +1,35 @@
+package org.springsource.samples.montyhall.resource;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springsource.samples.montyhall.controller.GameController;
+import org.springsource.samples.montyhall.domain.Game;
+import org.springsource.samples.montyhall.domain.Door;
+import org.springsource.samples.montyhall.domain.DoorStatus;
+import org.springsource.samples.montyhall.domain.Prize;
+
+import java.util.List;
+
+@Component
+public class DoorsResourceAssembler {
+
+ private final DoorResourceAssembler doorAssembler;
+
+ @Autowired
+ public DoorsResourceAssembler(DoorResourceAssembler assembler) {
+ this.doorAssembler = assembler;
+ }
+
+ public DoorsResource toResource(Game game) {
+ DoorsResource resource = new DoorsResource();
+ List<Door> doors = game.getDoors();
+ resource.doors[0] = this.doorAssembler.toResource(game,doors.get(0));
+ resource.doors[1] = this.doorAssembler.toResource(game,doors.get(1));
+ resource.doors[2] = this.doorAssembler.toResource(game,doors.get(2));
+ resource.add(linkTo(GameController.class).slash(game).slash("doors").withSelfRel());
+ return resource;
+ }
+}
12 src/main/java/org/springsource/samples/montyhall/resource/GameResource.java
@@ -0,0 +1,12 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.springsource.samples.montyhall.domain.GameStatus;
+
+public class GameResource extends ResourceSupport {
+
+ @JsonProperty("status")
+ GameStatus status;
+
+}
26 src/main/java/org/springsource/samples/montyhall/resource/GameResourceAssembler.java
@@ -0,0 +1,26 @@
+package org.springsource.samples.montyhall.resource;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
+import org.springframework.stereotype.Component;
+import org.springsource.samples.montyhall.controller.GameController;
+import org.springsource.samples.montyhall.domain.Game;
+
+@Component
+public class GameResourceAssembler extends ResourceAssemblerSupport<Game, GameResource> {
+
+ public GameResourceAssembler() {
+ super(GameController.class, GameResource.class);
+ }
+
+ @Override
+ public GameResource toResource(Game game) {
+ GameResource resource = createResource(game);
+ resource.status = game.getStatus();
+ resource.add(linkTo(GameController.class).slash(game).slash("doors").withRel("doors"));
+ resource.add(linkTo(GameController.class).slash(game).slash("history").withRel("history"));
+ return resource;
+ }
+}
+
14 src/main/java/org/springsource/samples/montyhall/resource/HistoryResource.java
@@ -0,0 +1,14 @@
+package org.springsource.samples.montyhall.resource;
+
+import org.springframework.hateoas.ResourceSupport;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import org.springsource.samples.montyhall.domain.GameEvent;
+
+
+public class HistoryResource extends ResourceSupport {
+
+ @JsonProperty("events")
+ GameEvent[] events;
+
+}
25 src/main/java/org/springsource/samples/montyhall/resource/HistoryResourceAssembler.java
@@ -0,0 +1,25 @@
+package org.springsource.samples.montyhall.resource;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
+import org.springframework.stereotype.Component;
+import org.springsource.samples.montyhall.controller.GameController;
+import org.springsource.samples.montyhall.domain.Game;
+import org.springsource.samples.montyhall.domain.GameEvent;
+import java.util.List;
+
+@Component
+public class HistoryResourceAssembler {
+
+ public HistoryResourceAssembler() {
+ }
+
+ public HistoryResource toResource(Game game) {
+ HistoryResource resource = new HistoryResource();
+ List<GameEvent> history = game.getHistory();
+ resource.events = history.toArray(new GameEvent[history.size()]);
+ resource.add(linkTo(GameController.class).slash(game).slash("history").withSelfRel());
+ return resource;
+ }
+}
17 src/main/java/org/springsource/samples/montyhall/service/ClickStreamService.java
@@ -0,0 +1,17 @@
+package org.springsource.samples.montyhall.service;
+
+import org.springframework.stereotype.Service;
+import org.springsource.samples.montyhall.domain.Game;
+import org.springsource.samples.montyhall.resource.ClickStreamResource;
+
+@Service
+public class ClickStreamService {
+
+ public ClickStreamService() {
+ // TODO - inject SI outbound channel
+ }
+
+ public void recordClickStream(Game game, ClickStreamResource clicks) {
+ // TODO, post as message on outbound channel
+ }
+}
33 src/main/java/org/springsource/samples/montyhall/web/MontyHallWebAppInitializer.java
@@ -0,0 +1,33 @@
+package org.springsource.samples.montyhall.web;
+
+import org.springframework.web.WebApplicationInitializer;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import java.util.Set;
+
+public class MontyHallWebAppInitializer implements WebApplicationInitializer {
+
+ @java.lang.Override
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ // Spring annotation based configuration from the 'config' package
+ AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
+ root.scan("org.springsource.samples.montyhall.config");
+
+ // Servlet setup...
+ servletContext.addListener(new ContextLoaderListener(root));
+
+ ServletRegistration.Dynamic appServlet =
+ servletContext.addServlet("appServlet", new DispatcherServlet(root));
+ appServlet.setLoadOnStartup(1);
+ Set<String> mappingConflicts = appServlet.addMapping("/");
+ if ( !mappingConflicts.isEmpty() ) {
+ throw new IllegalStateException ("Could not bind to '/', ensure Tomcat version > 7.0.14 ");
+ }
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.