2010年2月28日日曜日

[習作][GAE] GAEでオセロ対戦アプリ(になったらいいな) (2)

[習作][GAE] GAEでオセロ対戦アプリ(になったらいいな) (1)の続き。なかなか進まないなぁ・・・。
前回作成したMatchクラス(JDOオブジェクト)をCRUD操作するRESTfulサービスを、JAX-RS(Jerjey) で作成。HTTP経由でMatchクラスを操作できるようにする。
ユーザ認証については、(外部認証サービスで認証された)ユーザIDがセッションに格納されているという前提としている。

用意したのは、以下の7つの操作。JavaScriptクライアントからの利用を想定して、データを返却する場合はJSONを返すようにしている。
  • GET /matches - 全ゲームの取得
  • GET /matches/mymatches - ログインユーザのゲームを取得
  • POST /matches - 新規ゲームの開始(対戦相手を指定する)
  • GET /matches/{id} - idで指定されたゲームを取得
  • PUT /matches/{id} - 石を置く、またはパスする
  • PUT /matches/{id}/cancel - ゲームをキャンセルする
  • DELETE /matches/{id} - idで指定されたゲームを削除


// MatchResource.java
// Matchを操作するリソースクラス

package othello.service.resources;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import javax.jdo.JDODataStoreException;
import javax.jdo.PersistenceManager;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import com.sun.jersey.api.NotFoundException;

import othello.entity.Board;
import othello.entity.Match;
import othello.entity.PMF;
import othello.service.jaxb.MatchJaxbBean;
import othello.service.jaxb.MatchListJaxbBean;

@Path("/matches")
public class MatchResource {

@Context HttpServletRequest request;

/**
* 全ゲームを取得する
* @return
*/
@GET
@Produces("application/json")
public MatchListJaxbBean getAllMatches() {
PersistenceManager pm = PMF.get().getPersistenceManager();

// JDO Query "select from othello.entity.Match"
String query = "select from " + Match.class.getName();
List<Match> matchList = (List<Match>)pm.newQuery(query).execute();
MatchListJaxbBean listBean = new MatchListJaxbBean(matchList);
return listBean;
}

/**
* ログインユーザのゲームを取得する
* @return
*/
@GET @Path("/mymatches")
@Produces("application/json")
public MatchListJaxbBean getMyMatches() {
PersistenceManager pm = PMF.get().getPersistenceManager();

// セッションからログインユーザID取得
String userId = (String)request.getSession().getAttribute("userId");
if (userId == null) userId = "guest";

String query = "select from " + Match.class.getName();
List<Match> matchList = (List<Match>)pm.newQuery(query + " where player1 == \"" + userId + "\"").execute();
if (matchList == null) {
return new MatchListJaxbBean();
}
List<Match> matchList2 = (List<Match>)pm.newQuery(query + " where player2 == \"" + userId + "\"").execute();
for (Match match : matchList2) {
if (!matchList.contains(match)) matchList.add(match);
}

MatchListJaxbBean listBean = new MatchListJaxbBean(matchList);
return listBean;
}

/**
* 新規ゲーム開始
* @param opposite
* @return
* @throws URISyntaxException
*/
@POST
@Consumes("application/x-www-form-urlencoded")
public Response createMatch(@FormParam("opposite") String opposite) throws URISyntaxException {
PersistenceManager pm = PMF.get().getPersistenceManager();

// セッションからログインユーザID取得
String userId = (String)request.getSession().getAttribute("userId");
if (userId == null) userId = "guest";

// ログインユーザと opposite が対戦する新規ゲーム
Match match = new Match(userId, opposite);
try {
// Matchインスタンス保存
pm.makePersistent(match);
} catch (JDODataStoreException e) {
throw new WebApplicationException();
} finally {
pm.close();
}

return Response.seeOther(new URI(request.getRequestURL() + "/" + match.getId().toString())).build();
}

/**
* idで指定されたゲームを取得する
* @param id
* @return
*/
@GET @Path("/{id}")
@Produces("application/json")
public MatchJaxbBean getMatch(@PathParam("id") String id) {
PersistenceManager pm = PMF.get().getPersistenceManager();

Long idLong = null;
try {
idLong = Long.valueOf(id);
} catch (NumberFormatException e) {
throw new WebApplicationException(400);
}

Match match = null;
MatchJaxbBean bean;
try {
// オブジェクトIDからMatchインスタンスを取得
match = (Match)pm.getObjectById(Match.class, idLong);
if (match == null) {
throw new NotFoundException("Sorry, Match (" + id + ") not found.");
}
bean = new MatchJaxbBean(match);
} catch (JDODataStoreException e) {
throw new WebApplicationException();
} finally {
pm.close();
}

return bean;
}

/**
* 石を置く、またはパスする
* @param id
* @param row
* @param col
* @param pass
* @return
*/
@PUT @Path("/{id}")
public Response updateMatch(
@PathParam("id") String id, // ゲームID
@QueryParam("row") String row, // 石を置くセルの行
@QueryParam("col") String col, // 石を置くセルの列
@QueryParam("pass") String pass // パスの場合は "true" を指定
) {

PersistenceManager pm = PMF.get().getPersistenceManager();

// セッションからログインユーザID取得
String userId = (String)request.getSession().getAttribute("userId");
if (userId == null) userId = "guest";

Long idLong = null;
try {
idLong = Long.valueOf(id);
} catch (NumberFormatException e) {
throw new WebApplicationException(400);
}

Match match = null;
try {
// オブジェクトIDからMatchインスタンスを取得
match = (Match)pm.getObjectById(Match.class, idLong);
if (match == null) {
throw new NotFoundException("Sorry, Match (" + id + ") not found.");
}
// ログインユーザのターンでなければ 403 Forbidden
if (!match.getNextPlayer().equals(userId)) {
throw new WebApplicationException(403);
}
if (pass != null && pass.equals("true")) {
// パスの場合
// Matchインスタンスを更新
match.passTurn();
} else {
int r = Integer.parseInt(row);
int c = Integer.parseInt(col);
Match.Pos pos = new Match.Pos(r, c);
// Matchインスタンス更新
if (!match.putNextHand(pos)) throw new WebApplicationException(400);
// Blobフィールドは更新できないため、新たにBoardインスタンスを生成してセットし直す
Board updatedBoard = new Board(match.getBoard().getBoard());
match.setBoard(updatedBoard);
}
} catch (JDODataStoreException e) {
throw new WebApplicationException();
} finally {
pm.close();
}

return Response.ok().build();
}

/**
* ゲームをキャンセルする
* @param id
* @return
*/
@PUT @Path("/{id}/cancel")
public Response cancelMatch(
@PathParam("id") String id) {

PersistenceManager pm = PMF.get().getPersistenceManager();

// セッションからログインユーザID取得
String userId = (String)request.getSession().getAttribute("userId");
if (userId == null) userId = "guest";

Long idLong = null;
try {
idLong = Long.valueOf(id);
} catch (NumberFormatException e) {
throw new WebApplicationException(400);
}

Match match = null;
try {
// オブジェクトIDからMatchインスタンスを取得
match = (Match)pm.getObjectById(Match.class, idLong);
if (match == null) {
throw new NotFoundException("Sorry, Match (" + id + ") not found.");
}
// ログインユーザのゲームでなければ 403 Forbidden
if (!match.getPlayer1().equals(userId) && !match.getPlayer2().equals(userId)) {
throw new WebApplicationException(403);
}
// Matchインスタンス更新
match.setCancelled(true);
} catch (JDODataStoreException e) {
throw new WebApplicationException();
} finally {
pm.close();
}

return Response.ok().build();
}

/**
* ゲームを削除する
* @param id
* @return
*/
@DELETE @Path("/{id}")
public Response deleteMatch(@PathParam("id") String id) {
PersistenceManager pm = PMF.get().getPersistenceManager();

// セッションからログインユーザID取得
String userId = (String)request.getSession().getAttribute("userId");
if (userId == null) userId = "guest";

Long idLong = null;
try {
idLong = Long.valueOf(id);
} catch (NumberFormatException e) {
throw new WebApplicationException(400);
}

Match match = null;
try {
// オブジェクトIDからMatchインスタンスを取得
match = (Match)pm.getObjectById(Match.class, idLong);
if (match == null) {
throw new NotFoundException("Sorry, Match (" + id + ") not found.");
}
// ログインユーザのゲームでなければ 403 Forbidden
if (!match.getPlayer1().equals(userId) && !match.getPlayer2().equals(userId)) {
throw new WebApplicationException(403);
}
// Matchインスタンス削除
pm.deletePersistent(match);
} catch (JDODataStoreException e) {
throw new WebApplicationException();
} finally {
pm.close();
}

return Response.ok().build();
}
}


以下は戻り値となるJAXBビーンクラス。

// MatchListJaxbBean.java

package othello.service.jaxb;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import othello.entity.Match;

@XmlRootElement
public class MatchListJaxbBean {
private List<MatchJaxbBean> matches;

public MatchListJaxbBean() {
this.matches = new ArrayList<MatchJaxbBean>();
}

public MatchListJaxbBean(List<Match> matchList) {
this.matches = new ArrayList<MatchJaxbBean>();
for (Match match : matchList) {
MatchJaxbBean bean = new MatchJaxbBean(match);
matches.add(bean);
}
}

@XmlElementWrapper(name="matches")
@XmlElement(name="match")
public List<MatchJaxbBean> getMatches() {
return matches;
}

public void setMatches(List<MatchJaxbBean> matches) {
this.matches = matches;
}


}


// MatchJaxbBean.java

package othello.service.jaxb;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import othello.entity.Board;
import othello.entity.Match;

@XmlRootElement
public class MatchJaxbBean {

public static final String STATUS_FINISHED = "finished";
public static final String STATUS_CANCELLED = "cancelled";
public static final String STATUS_ONGOING = "ongoing";

private String id;
private String player1;
private String player2;
private String status;
private String turn;
private String nextPlayer;
private String whiteCount;
private String blackCount;
private int[][] board;

private List<PosJaxbBean> candidates;

public MatchJaxbBean() {
this.candidates = new ArrayList<PosJaxbBean>();
}

public MatchJaxbBean(Match match) {
this.id = match.getId().toString();
this.player1 = match.getPlayer1();
this.player2 = match.getPlayer2();
if (match.isFinished()) {
this.status = STATUS_FINISHED;
} else if (match.isCancelled()) {
this.status = STATUS_CANCELLED;
} else {
this.status = STATUS_ONGOING;
}
this.nextPlayer = match.getNextPlayer();
this.turn = Integer.toString(match.getTurn());
this.blackCount = Integer.toString(match.getBoard().getCount(Board.BLACK));
this.whiteCount = Integer.toString(match.getBoard().getCount(Board.WHITE));
this.board = match.getBoard().getBoard();
this.candidates = new ArrayList<PosJaxbBean>();
for (Match.Pos pos : match.getNextHands()) {
candidates.add(new PosJaxbBean(pos));
}
}

@XmlElement
public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

@XmlElement
public String getPlayer1() {
return player1;
}

public void setPlayer1(String player1) {
this.player1 = player1;
}

@XmlElement
public String getPlayer2() {
return player2;
}

public void setPlayer2(String player2) {
this.player2 = player2;
}

@XmlElement
public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

@XmlElement
public String getTurn() {
return turn;
}

public void setTurn(String turn) {
this.turn = turn;
}

@XmlElement
public String getNextPlayer() {
return nextPlayer;
}

public void setNextPlayer(String nextPlayer) {
this.nextPlayer = nextPlayer;
}

@XmlElement
public String getWhiteCount() {
return whiteCount;
}

public void setWhiteCount(String whiteCount) {
this.whiteCount = whiteCount;
}

@XmlElement
public String getBlackCount() {
return blackCount;
}

public void setBlackCount(String blackCount) {
this.blackCount = blackCount;
}


@XmlElement
public int[][] getBoard() {
return board;
}

public void setBoard(int[][] board) {
this.board = board;
}

@XmlElementWrapper(name="candidates")
@XmlElement(name="candidate")
public List<PosJaxbBean> getCandidates() {
return candidates;
}

public void setCandidates(List<PosJaxbBean> candidates) {
this.candidates = candidates;
}

}


// PosJaxbBean.java

package othello.service.jaxb;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import othello.entity.Match;

@XmlRootElement
public class PosJaxbBean {
private String row;
private String col;

public PosJaxbBean() { }

public PosJaxbBean(Match.Pos pos) {
this.row = Integer.toString(pos.getRow());
this.col = Integer.toString(pos.getCol());
}

@XmlElement
public String getRow() {
return row;
}

public void setRow(String row) {
this.row = row;
}

@XmlElement
public String getCol() {
return col;
}

public void setCol(String col) {
this.col = col;
}


}

0 件のコメント:

コメントを投稿