PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/src/bananaounces/remoting/services/game/GameService.java

https://bitbucket.org/drocco/bananaounces/
Java | 446 lines | 185 code | 63 blank | 198 comment | 27 complexity | 6b8e7f9dc7f18c152795ae68720eca44 MD5 | raw file
  1. package bananaounces.remoting.services.game;
  2. import java.io.UnsupportedEncodingException;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import org.restlet.Application;
  6. import org.restlet.Context;
  7. import org.restlet.Request;
  8. import org.restlet.Response;
  9. import org.restlet.Restlet;
  10. import org.restlet.data.ChallengeScheme;
  11. import org.restlet.routing.Router;
  12. import org.restlet.security.ChallengeAuthenticator;
  13. import org.restlet.security.LocalVerifier;
  14. import bananaounces.model.Game;
  15. import bananaounces.model.Player;
  16. import bananaounces.model.Tile;
  17. import bananaounces.remoting.RemotePlayer;
  18. import bananaounces.remoting.ServiceBase;
  19. import com.google.inject.Inject;
  20. import com.google.inject.Provider;
  21. /**
  22. * Service to handle incoming game requests. Stands between clients and
  23. * BananaouncesGame.
  24. * <p>
  25. * GameService is responsible for dispatching calls to the games in play. Rather
  26. * than calling Game instance methods correctly, resource classes should call
  27. * the corresponding methods in GameService, which will route the requests to
  28. * the appropriate game.
  29. *
  30. * @author Daniel Rocco
  31. * @author Justin Chester
  32. * @author Matthew Farmer
  33. * @version 0.5 Spring 2010
  34. */
  35. public class GameService extends Application {
  36. /**
  37. * Map game names to the Bananaounces game object for that game.
  38. */
  39. private static HashMap<String, Game> games = new HashMap<String, Game>();
  40. private final Provider<Game> gameProvider;
  41. private final Provider<RemotePlayer> playerProvider;
  42. /**
  43. * Game service constructor to create a game service on the default service
  44. * port. The service keeps track of the games currently in play and routes
  45. * incoming requests to the appropriate resource handlers.
  46. *
  47. * @param gameProvider
  48. * factory that provides instances of the Game class for creating
  49. * new games
  50. * @param playerProvider
  51. * factory that provides instances of the RemotePlayer class for
  52. * creating new players
  53. * @throws Exception
  54. * @see org.restlet.Application constructor
  55. */
  56. @Inject
  57. public GameService(Provider<Game> gameProvider,
  58. Provider<RemotePlayer> playerProvider) throws Exception {
  59. super();
  60. this.gameProvider = gameProvider;
  61. this.playerProvider = playerProvider;
  62. }
  63. /**
  64. * Map incoming requests to the appropriate resources. Also creates the
  65. * authenticator for authenticating privileged requests like peel.
  66. */
  67. @Override
  68. public Restlet createInboundRoot() {
  69. Router router = new Router(getContext());
  70. router.attach("/games/", GamesResource.class);
  71. router.attach("/games/{game}/", GamesResource.class);
  72. router.attach("/games/{game}/players/", PlayersResource.class);
  73. // the order is important here: Restlet 2.x defaults to "First match"
  74. // for templates.
  75. // see:
  76. // http://www.developer.com/java/web/article.php/10935_3875781_2/Leveraging-the-Restlet-Routing-System-for-Your-Java-Web-Services.htm
  77. router.attach("/games/{game}/players/{player};{callback}",
  78. PlayerResource.class);
  79. router.attach("/games/{game}/players/{player}", PlayerResource.class);
  80. router.attach("/games/{game}/pile/tile/{tile}", PileResource.class);
  81. router.attach("/games/{game}/pile/{tile}", PileResource.class);
  82. router.attach("/games/{game}/pile/", PileResource.class);
  83. this.authenticator = createAuthenticator();
  84. authenticator.setNext(router);
  85. return authenticator;
  86. }
  87. //
  88. // game service interface
  89. //
  90. /**
  91. * Attaches a new game to this service.
  92. *
  93. * @param gameName
  94. * the name of the game
  95. * @return true if the game was successfully added; false if a game with the
  96. * given name already exists
  97. */
  98. public boolean createGame(String gameName) {
  99. if (games.containsKey(gameName)) {
  100. return false;
  101. }
  102. games.put(gameName, gameProvider.get());
  103. return true;
  104. }
  105. /**
  106. * List the games that have been created on this service.
  107. *
  108. * @return a list of the game names attached to this service
  109. */
  110. public String listGames() {
  111. return games.keySet().toString();
  112. }
  113. /**
  114. * Test whether a game currently exists.
  115. *
  116. * @param gameName
  117. * the name of the game to check for existence
  118. * @return true if the game exists, false if it does not or if gameName is
  119. * null
  120. */
  121. public boolean containsGame(String gameName) {
  122. if (gameName == null) {
  123. return false;
  124. }
  125. return games.containsKey(gameName);
  126. }
  127. /**
  128. * Check to see if the given game has started; that is, has a valid split
  129. * call been sent to the game.
  130. *
  131. * @param gameName
  132. * the name of the game to check
  133. * @return true if the game has started, false if it has not or does not
  134. * exist
  135. */
  136. public boolean gameStarted(String gameName) {
  137. if (!games.containsKey(gameName)) {
  138. return false;
  139. }
  140. return games.get(gameName).gameStarted();
  141. }
  142. /**
  143. * Check to see if a player is registered with a game.
  144. *
  145. * @param gameName
  146. * the name of the game to check
  147. * @param playerName
  148. * the name of the player
  149. * @return true if the player is registered with the game, false otherwise
  150. */
  151. public boolean containsPlayer(String gameName, String playerName) {
  152. if (gameName == null || playerName == null || !containsGame(gameName)) {
  153. return false;
  154. }
  155. return getGame(gameName).hasPlayer(playerName);
  156. }
  157. /**
  158. * Retrieve a player from the specified game.
  159. *
  160. * @param gameName
  161. * the name of the game
  162. * @param playerName
  163. * the name of the player, which should uniquely identify them
  164. * @return the RemotePlayer object representing the specified player, or
  165. * null if the player does not exist
  166. */
  167. public RemotePlayer getPlayer(String gameName, String playerName) {
  168. return (RemotePlayer) getGame(gameName).getPlayer(playerName);
  169. }
  170. /**
  171. * Register a player with the specified game, creating the game if it does
  172. * not already exist.
  173. * <p>
  174. * Each player needs a unique name identifying them and a web service
  175. * callback URL that the game can use to send updates to the player.
  176. *
  177. * @param gameName
  178. * the name of the game to join; the corresponding game is
  179. * created if it does not already exist
  180. * @param playerName
  181. * the name of the player joining the game, which should be
  182. * unique
  183. * @param callback
  184. * the player's web service callback URL
  185. * @return the RemotePlayer object representing the specified player
  186. * @throws UnsupportedEncodingException
  187. * if any of the parameters could not be converted to URL-safe
  188. * utf-8
  189. */
  190. public Player registerPlayer(String gameName, String playerName,
  191. String callback) throws UnsupportedEncodingException {
  192. if (gameName == null || playerName == null) {
  193. return null;
  194. }
  195. if (!containsGame(gameName)) {
  196. createGame(gameName);
  197. }
  198. RemotePlayer player = playerProvider.get();
  199. player.setName(playerName);
  200. player.setGameName(gameName);
  201. player.setCallbackURL(callback);
  202. getGame(gameName).registerPlayer(player);
  203. return player;
  204. }
  205. /**
  206. * Get the status of the given game.
  207. *
  208. * @param gameName
  209. * the name of the game
  210. * @return the status of the game, or null if the game does not exist
  211. */
  212. public String getGameStatus(String gameName) {
  213. String result = null;
  214. if (containsGame(gameName)) {
  215. result = getGame(gameName).toString();
  216. }
  217. return result;
  218. }
  219. /**
  220. * Get the number of players registered for the given game.
  221. *
  222. * @param gameName
  223. * the name of the game
  224. * @return the number of players registered for the game; returns 0 if the
  225. * game does not exist
  226. */
  227. public int getPlayerCount(String gameName) {
  228. int result = 0;
  229. if (containsGame(gameName)) {
  230. result = getGame(gameName).getPlayerCount();
  231. }
  232. return result;
  233. }
  234. /**
  235. * Retrieve the list of players for the given game.
  236. *
  237. * @param gameName
  238. * the name of the game
  239. * @return the list of players currently registered for the game, or null if
  240. * the game does not exist
  241. */
  242. public List<String> getPlayers(String gameName) {
  243. return containsGame(gameName) ? getGame(gameName).getPlayers() : null;
  244. }
  245. /**
  246. * Call split on the given game, sending players their initial tile list and
  247. * starting the game.
  248. * <p>
  249. * Does nothing if the game does not exist.
  250. *
  251. * @param gameName
  252. * the game to start
  253. */
  254. public void split(String gameName) {
  255. if (containsGame(gameName)) {
  256. getGame(gameName).split();
  257. }
  258. }
  259. /**
  260. * Call peel on the given game, sending players their next tile.
  261. * <p>
  262. * Does nothing if the game does not exist.
  263. *
  264. * @param gameName
  265. * the game to peel
  266. */
  267. public void peel(String gameName) {
  268. if (containsGame(gameName)) {
  269. getGame(gameName).peel();
  270. }
  271. }
  272. /**
  273. * Get the size of the pile for the given game.
  274. *
  275. * @param gameName
  276. * the name of the game
  277. * @return the size of the pile for the game; returns 0 if the game does not
  278. * exist
  279. */
  280. public int getPileSize(String gameName) {
  281. int result = 0;
  282. if (containsGame(gameName)) {
  283. result = getGame(gameName).getPileSize();
  284. }
  285. return result;
  286. }
  287. /**
  288. * Check to see if a dump call is valid for the specified game. A dump is
  289. * valid if the pile has at least 3 tiles remaining.
  290. *
  291. * @param gameName
  292. * the game to check
  293. * @return true if a dump is valid, false if not or the game does not exist
  294. */
  295. public boolean canDump(String gameName) {
  296. return containsGame(gameName) ? getGame(gameName).canDump() : false;
  297. }
  298. /**
  299. * Dump a tile; the player exchanges the given tile for 3 replacements.
  300. * <p>
  301. * Does nothing if the game does not exist.
  302. *
  303. * @param gameName
  304. * the name of the game
  305. * @param playerName
  306. * the player dumping the tile
  307. * @param tile
  308. * the tile to return to the pile
  309. */
  310. public void dump(String gameName, String playerName, Tile tile) {
  311. if (containsGame(gameName)) {
  312. getGame(gameName).dump(playerName, tile);
  313. }
  314. }
  315. //
  316. // helper methods
  317. //
  318. /**
  319. * Retrieve a given game from this service's list of games.
  320. *
  321. * @param gameName
  322. * the name of the desired game
  323. * @return the game associated with the given name, or null if no such game
  324. * exists
  325. */
  326. private Game getGame(String gameName) {
  327. return games.get(gameName);
  328. }
  329. //
  330. // authentication and authorization
  331. //
  332. // adapted from
  333. // http://stackoverflow.com/questions/2217418/fine-grained-authentication-with-restlet
  334. private ChallengeAuthenticator authenticator;
  335. private LocalVerifier createVerifier() {
  336. LocalVerifier verifier = new LocalVerifier() {
  337. @Override
  338. public char[] getLocalSecret(String gamePlayerName) {
  339. if (gamePlayerName == null) {
  340. return null;
  341. }
  342. String[] gamePlayer = gamePlayerName.split("@", 2);
  343. RemotePlayer player = getPlayer(gamePlayer[1], gamePlayer[0]);
  344. return (player == null) ? null : player.getCallbackURL()
  345. .toCharArray();
  346. }
  347. };
  348. return verifier;
  349. }
  350. private ChallengeAuthenticator createAuthenticator() {
  351. Context context = getContext();
  352. boolean optional = true;
  353. ChallengeScheme challengeScheme = ChallengeScheme.HTTP_BASIC;
  354. String realm = "bananaounces";
  355. LocalVerifier verifier = createVerifier();
  356. ChallengeAuthenticator auth = new ChallengeAuthenticator(context,
  357. optional, challengeScheme, realm, verifier) {
  358. @Override
  359. protected boolean authenticate(Request request, Response response) {
  360. if (request.getChallengeResponse() == null) {
  361. return false;
  362. } else {
  363. return super.authenticate(request, response);
  364. }
  365. }
  366. };
  367. return auth;
  368. }
  369. public boolean authenticate(Request request, Response response) {
  370. if (!request.getClientInfo().isAuthenticated()) {
  371. authenticator.challenge(response, false);
  372. return false;
  373. }
  374. return true;
  375. }
  376. //
  377. // main
  378. //
  379. public static void main(String[] args) throws Exception {
  380. GameServiceModule.startGameService(new GameServiceModule(),
  381. ServiceBase.DEFAULT_SERVICE_PORT, "/v1");
  382. }
  383. }