webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sun, 25 Apr 2010 21:20:09 +0200
changeset 238 a4f6aca595e8
parent 189 6245e1b634aa
child 264 d60370059c3c
permissions -rw-r--r--
Support for permission.resign to allow the emailer cancel long running games
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * The contents of this file are subject to the terms of either the GNU
     5  * General Public License Version 2 only ("GPL") or the Common
     6  * Development and Distribution License("CDDL") (collectively, the
     7  * "License"). You may not use this file except in compliance with the
     8  * License. You can obtain a copy of the License at
     9  * http://www.netbeans.org/cddl-gplv2.html
    10  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    11  * specific language governing permissions and limitations under the
    12  * License.  When distributing the software, include this License Header
    13  * Notice in each file and include the License file at
    14  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    15  * particular file as subject to the "Classpath" exception as provided
    16  * by Sun in the GPL Version 2 section of the License file that
    17  * accompanied this code. If applicable, add the following below the
    18  * License Header, with the fields enclosed by brackets [] replaced by
    19  * your own identifying information:
    20  * "Portions Copyrighted [year] [name of copyright owner]"
    21  *
    22  * Contributor(s):
    23  *
    24  * Portions Copyrighted 2009 Jaroslav Tulach
    25  */
    26 
    27 package cz.xelfi.quoridor.webidor.resources;
    28 
    29 import com.sun.jersey.api.json.JSONWithPadding;
    30 import cz.xelfi.quoridor.IllegalPositionException;
    31 import cz.xelfi.quoridor.webidor.*;
    32 import cz.xelfi.quoridor.Move;
    33 import java.io.BufferedReader;
    34 import java.io.BufferedWriter;
    35 import java.io.File;
    36 import java.io.FileInputStream;
    37 import java.io.FileOutputStream;
    38 import java.io.IOException;
    39 import java.io.InputStream;
    40 import java.io.InputStreamReader;
    41 import java.io.OutputStreamWriter;
    42 import java.io.PrintWriter;
    43 import java.io.Writer;
    44 import java.util.ArrayList;
    45 import java.util.Collections;
    46 import java.util.Date;
    47 import java.util.List;
    48 import java.util.logging.Level;
    49 import java.util.logging.Logger;
    50 import java.util.regex.Matcher;
    51 import java.util.regex.Pattern;
    52 import javax.ws.rs.DefaultValue;
    53 import javax.ws.rs.GET;
    54 import javax.ws.rs.POST;
    55 import javax.ws.rs.PUT;
    56 import javax.ws.rs.Path;
    57 import javax.ws.rs.PathParam;
    58 import javax.ws.rs.Produces;
    59 import javax.ws.rs.QueryParam;
    60 import javax.ws.rs.WebApplicationException;
    61 import javax.ws.rs.core.GenericEntity;
    62 import javax.ws.rs.core.MediaType;
    63 import javax.ws.rs.core.Response.Status;
    64 
    65 /**
    66  *
    67  * @author Jaroslav Tulach <jtulach@netbeans.org>
    68  */
    69 public final class Games {
    70     private final Quoridor quoridor;
    71     private final List<Game> games = new ArrayList<Game>();
    72     private final File dir;
    73     private static final Logger LOG = Logger.getLogger(Games.class.getName());
    74 
    75     public Games(File dir, Quoridor quoridor) {
    76         this.dir = dir;
    77         this.quoridor = quoridor;
    78         File[] arr = dir.listFiles();
    79         if (arr != null) {
    80             for (File f : arr) {
    81                 try {
    82                     Game g = readGame(f);
    83                     games.add(g);
    84                 } catch (IOException ex) {
    85                     LOG.log(Level.WARNING, "Wrong game in " + f, ex);
    86                 }
    87             }
    88         }
    89     }
    90 
    91     @POST
    92     @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
    93     public GameId createGame(
    94         @QueryParam("loginID") String id,
    95         @QueryParam("white") String white,
    96         @QueryParam("black") String black
    97     ) throws IOException {
    98         String logUser = quoridor.isLoggedIn(id);
    99         if (logUser == null) {
   100             throw new WebApplicationException(Status.UNAUTHORIZED);
   101         }
   102         if (white == null) {
   103             throw new WebApplicationException(Status.PRECONDITION_FAILED);
   104         }
   105         if (black == null) {
   106             throw new WebApplicationException(Status.PRECONDITION_FAILED);
   107         }
   108         if (!logUser.equals(white) && !logUser.equals(black)) {
   109             throw new WebApplicationException(Status.PRECONDITION_FAILED);
   110         }
   111 
   112         Game g = new Game(white, black);
   113         storeGame(g);
   114         games.add(g);
   115         return g.getId();
   116     }
   117 
   118     @GET
   119     @Path("{id}")
   120     @Produces(MediaType.TEXT_PLAIN)
   121     public String getBoard(
   122         @PathParam("id") String id,
   123         @QueryParam("move") @DefaultValue("-1") int move
   124     ) {
   125         Game g = findGame(id, move);
   126         if (g == null) {
   127             throw new IllegalArgumentException("Unknown game " + id);
   128         }
   129         return g.getBoard().toString();
   130     }
   131 
   132     public Game getBoardInfo(
   133         @QueryParam("loginID") @DefaultValue("") String loginId,
   134         @PathParam("id") String id,
   135         @QueryParam("move") @DefaultValue("-1") int move
   136     ) throws IOException {
   137         Game g = findGame(id, move);
   138         if (canSee(g.getId(), loginId)) {
   139             return g;
   140         }
   141         throw new WebApplicationException(Status.UNAUTHORIZED);
   142     }
   143 
   144     @GET
   145     @Path("{id}")
   146     @Produces({ "application/x-javascript", MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   147     public JSONWithPadding getBoardInfo(
   148         @QueryParam("callback") String callback,
   149         @QueryParam("loginID") @DefaultValue("") String loginId,
   150         @PathParam("id") String id,
   151         @QueryParam("move") @DefaultValue("-1") int move
   152     ) throws IOException {
   153         return new JSONWithPadding(getBoardInfo(loginId, id, move), callback);
   154     }
   155 
   156 
   157     private boolean canSee(GameId id, String loginId) throws IOException {
   158         if (!id.isFinished()) {
   159             return true;
   160         }
   161         String logUser = quoridor.isLoggedIn(loginId);
   162         if (logUser == null) {
   163             return false;
   164         }
   165         if (logUser.equals(id.getWhite())) {
   166             return true;
   167         }
   168         if (logUser.equals(id.getBlack())) {
   169             return true;
   170         }
   171         User info = quoridor.getUsers().getUserInfo(loginId, logUser);
   172         if (info != null && info.hasPermission("games")) {
   173             return true;
   174         }
   175         return false;
   176     }
   177 
   178     @PUT
   179     @Path("{id}")
   180     @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   181     public GameId applyMove(
   182         @QueryParam("loginID") String loginId,
   183         @PathParam("id") String id,
   184         @QueryParam("player") String player,
   185         @QueryParam("move") String move,
   186         @QueryParam("comment") String comment
   187     ) throws IllegalPositionException, IOException {
   188         String logUser = quoridor.isLoggedIn(loginId);
   189         if (logUser == null) {
   190             throw new WebApplicationException(Status.UNAUTHORIZED);
   191         }
   192         if (!logUser.equals(player)) {
   193             User info = quoridor.getUsers().getUserInfo(loginId, logUser);
   194             if (info == null || !info.hasPermission("resign")) {
   195                 throw new WebApplicationException(Status.UNAUTHORIZED);
   196             }
   197         }
   198         if (comment == null && move == null) {
   199             throw new WebApplicationException(Status.BAD_REQUEST);
   200         }
   201 
   202         Game g = findGame(id);
   203         if (g == null) {
   204             throw new IllegalArgumentException("Unknown game " + id);
   205         }
   206         if (move != null) {
   207             Move m = Move.valueOf(move);
   208             g.apply(player, m, new Date());
   209         }
   210         if (comment != null) {
   211             g.comment(player, comment, new Date());
   212         }
   213         try {
   214             storeGame(g);
   215         } catch (IOException ex) {
   216             LOG.log(Level.WARNING, "Cannot store game " + id, ex);
   217         }
   218         return g.getId();
   219     }
   220 
   221     @GET
   222     @Produces({ "application/x-javascript", MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   223     public JSONWithPadding listGames(
   224         @QueryParam("callback") String callback,
   225         @DefaultValue("") @QueryParam("loginID") String loginId,
   226         @DefaultValue("") @QueryParam("status") String status
   227     ) throws IOException {
   228         List<GameId> arr = new ArrayList<GameId>(games.size());
   229         for (Game g : games) {
   230             if (!canSee(g.getId(), loginId)) {
   231                 continue;
   232             }
   233             if (status.length() == 0 || g.getId().getStatus().toString().equals(status)) {
   234                 arr.add(g.getId());
   235             }
   236         }
   237         Collections.sort(arr, GameId.NEWEST_FIRST);
   238         return new JSONWithPadding(new GenericEntity<List<GameId>>(arr) {}, callback);
   239     }
   240 
   241     public List<Game> getGames() {
   242         return games;
   243     }
   244 
   245     private Game findGame(String id) {
   246         for (Game g : games) {
   247             if (g.getId().getId().equals(id)) {
   248                 return g;
   249             }
   250         }
   251         return null;
   252     }
   253     private Game findGame(String id, int move) {
   254         Game g = findGame(id);
   255         if (g == null) {
   256             throw new IllegalArgumentException("Unknown game " + id);
   257         }
   258         try {
   259             return move == -1 ? g : g.snapshot(move);
   260         } catch (IllegalPositionException ex) {
   261             Logger.getLogger(Games.class.getName()).log(Level.SEVERE, null, ex);
   262             return null;
   263         }
   264     }
   265 
   266     private static final Pattern saidWho = Pattern.compile("# *([^ ]*) *@(.*):$");
   267 
   268     private Game readGame(File f) throws IOException {
   269         InputStream is = new FileInputStream(f);
   270         BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   271         String white = null;
   272         String black = null;
   273         Game g = null;
   274         String who = null;
   275         String when = null;
   276         for (;;) {
   277             String line = r.readLine();
   278             if (line == null) {
   279                 line = "finish";
   280             }
   281             line = line.trim();
   282             if (line.length() == 0) {
   283                 continue;
   284             }
   285             if (line.startsWith("# white: ")) {
   286                 white = line.substring(9);
   287                 continue;
   288             }
   289             if (line.startsWith("# black: ")) {
   290                 black = line.substring(9);
   291                 continue;
   292             }
   293             if (line.startsWith("#")) {
   294                 Matcher m =saidWho.matcher(line);
   295                 if (m.matches()) {
   296                     who = m.group(1);
   297                     when = m.group(2);
   298                     continue;
   299                 }
   300                 if (g == null) {
   301                     continue;
   302                 }
   303                 if (line.startsWith("# ")) {
   304                     line = line.substring(2);
   305                 } else {
   306                     line = line.substring(1);
   307                 }
   308                 Date d = new Date();
   309                 try {
   310                     if (when != null) {
   311                         d = new Date(Date.parse(when));
   312                     }
   313                 } catch (IllegalArgumentException ex) {
   314                     LOG.warning("Unparseable date " + when + " in " + f);
   315                 }
   316                 g.comment(who, line, d);
   317                 who = null;
   318                 when = null;
   319                 continue;
   320             }
   321             if (white == null || black == null) {
   322                 throw new IOException("Missing white and black identification in " + f);
   323             }
   324             if (g == null) {
   325                 GameId id = new GameId(f.getName(), white, black, new Date(f.lastModified()), new Date(f.lastModified()), GameStatus.whiteMove, 0, false);
   326                 g = new Game(id);
   327             }
   328             int hash = line.indexOf('#');
   329             if (hash >= 0) {
   330                 line = line.substring(0, hash);
   331             }
   332             if (line.equals("finish")) {
   333                 break;
   334             }
   335             String[] moves = line.split(" ");
   336             if (moves.length == 0) {
   337                 continue;
   338             }
   339             if (moves.length > 2) {
   340                 throw new IOException("Too much moves on line: " + line);
   341             }
   342             try {
   343                 if (!"...".equals(moves[0])) {
   344                     g.apply(white, Move.valueOf(moves[0]), null);
   345                 }
   346                 if (moves.length == 2) {
   347                     g.apply(black, Move.valueOf(moves[1]), null);
   348                 }
   349             } catch (IllegalPositionException ex) {
   350                 throw new IOException("Wrong move: " + ex.getMessage());
   351             }
   352         }
   353         if (g == null) {
   354             throw new IOException("No moves in " + f);
   355         }
   356         return g;
   357     }
   358 
   359     private void storeGame(Game g) throws IOException {
   360         dir.mkdirs();
   361         File f = new File(dir, g.getId().getId());
   362         storeGame(g, f);
   363     }
   364 
   365     final void storeGame(Game g, File f) throws IOException {
   366         FileOutputStream os = new FileOutputStream(f);
   367         Writer w = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
   368         PrintWriter pw = new PrintWriter(w);
   369         pw.println("# white: " + g.getId().getWhite());
   370         pw.println("# black: " + g.getId().getBlack());
   371         pw.println("# status: " + g.getId().getStatus());
   372         int cnt = 0;
   373         boolean separate = true;
   374         for (CommentedMove m : g.getMoves()) {
   375             if (!separate && cnt % 2 == 1) {
   376                 pw.print("... ");
   377             }
   378             separate = true;
   379             pw.print(m.getMove().toString());
   380             List<Note> notes = m.getComments();
   381             if (notes != null) {
   382                 separate = false;
   383                 pw.println();
   384                 for (Note n : notes) {
   385                     pw.print ("# ");
   386                     pw.print(n.getWho());
   387                     pw.print("@");
   388                     pw.print(n.getWhen());
   389                     pw.println(":");
   390                     for (String l : n.getComment().split("\n")) {
   391                         pw.print("# ");
   392                         pw.println(l);
   393                     }
   394                 }
   395             }
   396 
   397             cnt++;
   398 
   399             if (separate) {
   400                 if (cnt % 2 == 0) {
   401                     pw.println();
   402                 } else {
   403                     pw.print(' ');
   404                 }
   405             }
   406         }
   407         pw.println();
   408         pw.println();
   409         pw.flush();
   410         pw.close();
   411         w.close();
   412     }
   413 }