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