freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 04 Mar 2010 19:37:58 +0100
changeset 233 ecddc9f373bb
parent 220 fca206f8e5ef
child 234 0a71b6bd786f
permissions -rw-r--r--
Changing an email needs to be confirmed by receiving an email and responding to an URL send inside it
     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.freemarkerdor;
    28 
    29 import com.sun.jersey.api.client.Client;
    30 import com.sun.jersey.api.client.UniformInterfaceException;
    31 import com.sun.jersey.api.client.WebResource;
    32 import com.sun.jersey.api.container.httpserver.HttpServerFactory;
    33 import com.sun.jersey.api.core.PackagesResourceConfig;
    34 import com.sun.jersey.api.core.ResourceConfig;
    35 import com.sun.jersey.api.view.Viewable;
    36 import com.sun.net.httpserver.HttpServer;
    37 import cz.xelfi.quoridor.Board;
    38 import cz.xelfi.quoridor.IllegalPositionException;
    39 import java.io.IOException;
    40 import java.io.InputStream;
    41 import java.io.StringWriter;
    42 import java.net.URI;
    43 import java.text.MessageFormat;
    44 import java.util.Date;
    45 import java.util.HashMap;
    46 import java.util.List;
    47 import java.util.Locale;
    48 import java.util.Map;
    49 import java.util.MissingResourceException;
    50 import java.util.Properties;
    51 import java.util.ResourceBundle;
    52 import java.util.concurrent.Callable;
    53 import javax.ws.rs.DefaultValue;
    54 import javax.ws.rs.FormParam;
    55 import javax.ws.rs.GET;
    56 import javax.ws.rs.POST;
    57 import javax.ws.rs.Path;
    58 import javax.ws.rs.PathParam;
    59 import javax.ws.rs.Produces;
    60 import javax.ws.rs.QueryParam;
    61 import javax.ws.rs.core.CacheControl;
    62 import javax.ws.rs.core.Context;
    63 import javax.ws.rs.core.Cookie;
    64 import javax.ws.rs.core.HttpHeaders;
    65 import javax.ws.rs.core.MediaType;
    66 import javax.ws.rs.core.NewCookie;
    67 import javax.ws.rs.core.Response;
    68 import javax.ws.rs.core.Response.ResponseBuilder;
    69 import org.openide.util.Exceptions;
    70 import org.w3c.dom.Document;
    71 
    72 /**
    73  *
    74  * @author Jaroslav Tulach <jtulach@netbeans.org>
    75  */
    76 @Path("/")
    77 public final class UI {
    78     private static final String version;
    79     static {
    80         Properties p = new Properties();
    81         try {
    82             InputStream is = FreemarkerProcessor.class.getResourceAsStream("/META-INF/maven/cz.xelfi.quoridor/freemarkerdor/pom.properties"); // NOI18N
    83             if (is != null) {
    84                 p.load(is);
    85             }
    86         } catch (IOException ex) {
    87             ex.printStackTrace();
    88         }
    89         version = p.getProperty("version", "unknown"); // NOI18N
    90     }
    91     private static WebResource base;
    92     private static WebResource stat;
    93     private static WebResource web;
    94     private static Requests requests;
    95 
    96     @Context
    97     private HttpHeaders headers;
    98     private UserInfo user;
    99     private String uuid;
   100 
   101     public UI() {
   102     }
   103 
   104     private String login() {
   105         Cookie cookie = headers.getCookies().get("login");
   106         if (cookie != null) {
   107             return cookie.getValue();
   108         }
   109         return null;
   110     }
   111 
   112     private Viewable checkLogin() {
   113         String id = login();
   114         if (id != null) {
   115             UserInfo us;
   116             try {
   117                 us = base.path("users").queryParam("loginID", id).
   118                     accept(MediaType.TEXT_XML).get(UserInfo.class);
   119             } catch (Exception ex) {
   120                 ex.printStackTrace();
   121                 us = null;
   122             }
   123             if (us != null && us.getId().length() > 0) {
   124                 user = us;
   125                 uuid = id;
   126                 return null;
   127             }
   128         }
   129         return viewable("login.fmt", null);
   130     }
   131 
   132     @POST
   133     @Path("login")
   134     @Produces(MediaType.TEXT_HTML)
   135     public Response login(
   136         @FormParam("name") String name, @FormParam("password") String password
   137     ) throws Exception {
   138         uuid = base.path("login").queryParam("name", name).queryParam("password", password).
   139             accept(MediaType.TEXT_PLAIN).put(String.class);
   140         if (uuid != null) {
   141             user = new UserInfo(name);
   142             NewCookie nc = new NewCookie("login", uuid, null, null, null, 3600 * 24 * 7, false);
   143             return Response.ok().cookie(nc).entity(viewable("login.fmt", null)).build();
   144         } else {
   145             Viewable v = viewable("login.fmt", null, "message", "Invalid name or password: " + name);
   146             return Response.status(1).entity(v).build();
   147         }
   148     }
   149 
   150     @GET
   151     @Produces(MediaType.TEXT_HTML)
   152     public Response welcome(@QueryParam("maxItems") @DefaultValue("10") int maxItems) {
   153         Viewable v = checkLogin();
   154         ResponseBuilder resp = Response.ok();
   155         if (v == null) {
   156             v = welcomeImpl("maxItems", maxItems);
   157         }
   158         CacheControl cc = new CacheControl();
   159         cc.setNoCache(true);
   160         resp.cacheControl(cc);
   161         return resp.entity(v).build();
   162     }
   163 
   164     @GET
   165     @Path("games/{id}.png")
   166     @Produces("image/png")
   167     public Response getBoardImage(
   168         @PathParam("id") String id,
   169         @QueryParam("fieldSize") @DefaultValue("50") int fieldSize,
   170         @QueryParam("move") @DefaultValue("-1") int move
   171     ) throws IllegalPositionException {
   172         WebResource wr = base.path("games").path(id);
   173         if (move != -1) {
   174             wr = wr.queryParam("move", "" + move);
   175         }
   176         String txt = wr.accept(MediaType.TEXT_PLAIN).get(String.class);
   177         Board b = Board.valueOf(txt);
   178 //        Board b = new Board(txt);
   179         ResponseBuilder resp = Response.ok();
   180         CacheControl cc = new CacheControl();
   181         cc.setNoCache(true);
   182         resp.cacheControl(cc);
   183         return resp.entity(BoardImage.draw(b, fieldSize)).build();
   184     }
   185 
   186 
   187     private Response board(String id) {
   188         return board(id, "", null);
   189     }
   190     @GET
   191     @Path("games/{id}/")
   192     @Produces(MediaType.TEXT_HTML)
   193     public Response board(
   194         @PathParam("id") String id,
   195         @QueryParam("format") @DefaultValue("") String format,
   196         @QueryParam("move") @DefaultValue("-1") String move
   197     ) {
   198         return board(id, null, format, move);
   199     }
   200     private Response board(@PathParam("id") String id, String msg, String format, String m) {
   201         Viewable v = checkLogin();
   202         if (v != null) {
   203             return Response.ok().entity(v).build();
   204         }
   205         int move;
   206         try {
   207             move = Integer.parseInt(m);
   208         } catch (NumberFormatException ex) {
   209             move = -1;
   210         }
   211         WebResource url = base.path("games").queryParam("loginID", uuid).path(id);
   212         if (move >= 0) {
   213             url = url.queryParam("move", "" + move);
   214         }
   215         ResponseBuilder resp = Response.ok();
   216         CacheControl cc = new CacheControl();
   217         cc.setNoCache(true);
   218         resp.cacheControl(cc);
   219         Cookie cFormat = headers.getCookies().get("format");
   220         if (format.length() == 0) {
   221             if (cFormat != null) {
   222                 format = cFormat.getValue();
   223             } else {
   224                 if (isMobile(headers)) {
   225                     format = "small";
   226                 }
   227             }
   228         } else {
   229             if (cFormat == null || !format.equals(cFormat.getValue())) {
   230                 resp.cookie(new NewCookie("format", format));
   231             }
   232         }
   233 
   234         Document doc = url.accept(MediaType.TEXT_XML).get(Document.class);
   235         Board b;
   236         String t = doc.getElementsByTagName("board").item(0).getTextContent();
   237         try {
   238             b = Board.valueOf(doc.getElementsByTagName("board").item(0).getTextContent());
   239         } catch (Exception ex) {
   240 //            b = new Board(t);
   241 //        } catch (IllegalStateException ex) {
   242             Exceptions.printStackTrace(ex);
   243             b = Board.empty();
   244         }
   245         String bCode = null;
   246         try{
   247             bCode = stat.path("openings").path(b.getCode()+".check").queryParam("loginID", user.getId()).accept(MediaType.TEXT_PLAIN).get(String.class);
   248         }catch(Exception e){
   249             bCode = null;
   250         }
   251         if(bCode == null || "".equals(bCode))
   252             v = viewable("game.fmt", doc, "message", msg, "format", format, "board", b,"textPicture", boardToPicture(b));
   253         else
   254             v = viewable("game.fmt", doc, "message", msg, "format", format, "board", b,"textPicture", boardToPicture(b),"bCode", bCode);
   255         return resp.entity(v).build();
   256     }
   257 
   258     private static String boardToPicture(Board b) {
   259         StringWriter w = new StringWriter();
   260         try {
   261             b.write(w);
   262         } catch (IOException ex) {
   263             return ex.toString();
   264         }
   265         return w.toString();
   266     }
   267 
   268     @GET
   269     @Path("games/{id}/move")
   270     @Produces(MediaType.TEXT_HTML)
   271     public Response move(
   272         @PathParam("id") String id,
   273         @QueryParam("type") String type,
   274         @QueryParam("direction") String direction,
   275         @QueryParam("direction-next") @DefaultValue("") String directionNext,
   276         @QueryParam("column") @DefaultValue("") String column,
   277         @QueryParam("row") @DefaultValue("") String row
   278     ) {
   279         Viewable v = checkLogin();
   280         if (v != null) {
   281             return Response.ok().entity(v).build();
   282         }
   283         WebResource wr = base.path("games").path(id).
   284             queryParam("loginID", uuid).
   285             queryParam("player", user.getId());
   286         try {
   287             if (type.equals("resign")) {
   288                 wr.queryParam("move", "RESIGN").put();
   289                 return board(id);
   290             }
   291             if (type.equals("fence")) {
   292                 wr.queryParam("move", direction.charAt(0) + column + row).put();
   293                 return board(id);
   294             }
   295             if (type.equals("move")) {
   296                 wr.queryParam("move", direction + directionNext).put();
   297                 return board(id);
   298             }
   299         } catch (UniformInterfaceException ex) {
   300             return board(id, "WRONG_MOVE", "-1");
   301         }
   302         return board(id);
   303     }
   304 
   305     @GET
   306     @Path("games/{id}/comment")
   307     @Produces(MediaType.TEXT_HTML)
   308     public Response comment(
   309         @PathParam("id") String id,
   310         @QueryParam("comment") String comment
   311     ) {
   312         Viewable v = checkLogin();
   313         if (v != null) {
   314             return Response.ok().entity(v).build();
   315         }
   316         WebResource wr = base.path("games").path(id).
   317             queryParam("loginID", uuid).
   318             queryParam("player", user.getId()).
   319             queryParam("comment", comment);
   320         wr.put();
   321         
   322         return board(id);
   323     }
   324 
   325     @GET
   326     @Path("games/create")
   327     @Produces(MediaType.TEXT_HTML)
   328     public Response create(
   329         @QueryParam("white") String white,
   330         @QueryParam("black") String black
   331     ) {
   332         Viewable v = checkLogin();
   333         if (v != null) {
   334             return Response.status(Response.Status.FORBIDDEN).entity(v).build();
   335         }
   336 
   337         if (user.getId().equals(white) || user.getId().equals(black)) {
   338             Object obj =
   339                 base.path("games").
   340                 queryParam("loginID", uuid).
   341                 queryParam("white", white).
   342                 queryParam("black", black).accept(MediaType.TEXT_XML).post(Document.class);
   343             return Response.ok(welcomeImpl()).build();
   344         } else {
   345             return Response.status(Response.Status.NOT_FOUND).
   346                 entity(welcomeImpl("message", "You (" + user.getId() + ") must be white or black!")).build();
   347         }
   348     }
   349 
   350     private Viewable welcomeImpl(Object... args) {
   351         final Document got = base.path("games").queryParam("loginID", uuid).accept(MediaType.TEXT_XML).get(Document.class);
   352         return viewable("index.fmt", got, args);
   353     }
   354 
   355     @Path("requests")
   356     public Requests getRequests() {
   357         if (requests == null) {
   358             requests = new Requests(web.path("requests"));
   359         }
   360         return requests;
   361     }
   362 
   363 
   364     @GET
   365     @Path("options")
   366     public Response changeOptions(
   367         @QueryParam("email") String email,
   368         @QueryParam("language") String language,
   369         @QueryParam("verified") String verified
   370     ) throws IOException {
   371         Viewable v = checkLogin();
   372         if (v != null) {
   373             return Response.status(Response.Status.FORBIDDEN).entity(v).build();
   374         }
   375 
   376         if (email != null) {
   377             if (getRequests().isVerified(verified)) {
   378                 UserInfo ui = base.path("users/" + user.getId()).
   379                     queryParam("loginID", uuid).
   380                     queryParam("name", "email").
   381                     queryParam("value", email).accept(MediaType.TEXT_XML).post(UserInfo.class);
   382             } else {
   383                 WebResource request;
   384                 request = web.path("options").queryParam("name", "email").queryParam("email", email);
   385                 URI callback = getRequests().register(login(), request);
   386 
   387                 ResourceBundle rb = bundle(null);
   388                 String subject = rb.getString("MSG_ChangeEmailSubject");
   389                 String text = MessageFormat.format(rb.getString("MSG_ChangeEmailText"), user.getId(), callback);
   390                 EmailService.getDefault().sendEmail(email, subject, text);
   391                 return Response.ok(viewable("email.fmt", null)).build();
   392 
   393             }
   394         }
   395 
   396         if (language != null) {
   397             UserInfo ui = base.path("users/" + user.getId()).
   398                 queryParam("loginID", uuid).
   399                 queryParam("name", "language").
   400                 queryParam("value", language).
   401                 accept(MediaType.TEXT_XML).post(UserInfo.class);
   402         }
   403 
   404         return welcome(10);
   405     }
   406 
   407     @GET
   408     @Path("elo")
   409     @Produces(MediaType.TEXT_HTML)
   410     public Response getEloList(
   411             @QueryParam("historyId") @DefaultValue("0") Integer historyId){
   412         Viewable v = checkLogin();
   413         if (v != null) {
   414             return Response.status(Response.Status.FORBIDDEN).entity(v).build();
   415         }
   416         final Document got = stat.path("elo").path("list").path(historyId.toString()).accept(MediaType.TEXT_XML).get(Document.class);
   417         return Response.ok(viewable("elo.fmt", got, "historyId", historyId)).build();
   418     }
   419     
   420     @GET
   421     @Path("openings")
   422     @Produces(MediaType.TEXT_HTML)
   423     public Response getOpeningRoot(){
   424         return getOpeningNode("ROOT");
   425     }
   426 
   427     @GET
   428     @Path("openings/{code}")
   429     @Produces(MediaType.TEXT_HTML)
   430     public Response getOpeningNode(@PathParam("code") String code){
   431         Viewable v = checkLogin();
   432         if (v != null) {
   433             return Response.status(Response.Status.FORBIDDEN).entity(v).build();
   434         }
   435         final Document got = stat.path("openings").path(code).queryParam("loginID", user.getId()).accept(MediaType.TEXT_XML).get(Document.class);
   436         Board b;
   437         try {
   438             b = Board.valueOf(got.getElementsByTagName("nodeCode").item(0).getTextContent());
   439         } catch (Exception ex) {
   440             Exceptions.printStackTrace(ex);
   441             b = Board.empty();
   442         }
   443         return Response.ok(viewable("openings.fmt", got, "whitefences",b.getPlayers().get(0).getFences(),"blackfences",b.getPlayers().get(1).getFences())).build();
   444     }
   445 
   446     @GET
   447     @Path("openings/{code}/{status}")
   448     @Produces(MediaType.TEXT_HTML)
   449     public Response getOpeningNodeGames(@PathParam("code") String code, @PathParam("status") String status){
   450         Viewable v = checkLogin();
   451         if (v != null) {
   452             return Response.status(Response.Status.FORBIDDEN).entity(v).build();
   453         }
   454         final Document got = stat.path("openings").path(code).path(status).queryParam("loginID", user.getId()).accept(MediaType.TEXT_XML).get(Document.class);
   455         return Response.ok(viewable("opening_games.fmt", got,"code",code,"color",status)).build();
   456     }
   457 
   458     @GET
   459     @Path("openings/{code}.png")
   460     @Produces("image/png")
   461     public Response getOpeningBoardImage(
   462         @PathParam("code") String code,
   463         @QueryParam("fieldSize") @DefaultValue("40") int fieldSize
   464     ) throws IllegalPositionException {
   465         Board b = Board.valueOf(code);
   466         ResponseBuilder resp = Response.ok();
   467         CacheControl cc = new CacheControl();
   468         cc.setNoCache(true);
   469         resp.cacheControl(cc);
   470         return resp.entity(BoardImage.draw(b, fieldSize)).build();
   471     }
   472 
   473     //
   474     // start the server
   475     //
   476 
   477     public static void main(String[] args) throws Exception {
   478         int port = 9333;
   479         if (args.length > 1) {
   480             port = Integer.parseInt(args[0]);
   481         }
   482         String remoteAPI = args.length >= 2 ? args[1] : null;
   483         String remoteStatistics = args.length >= 3 ? args[2] : null;
   484 
   485         Locale.setDefault(Locale.ROOT);
   486 
   487         Callable<Void> r = startServers(port, remoteAPI, remoteStatistics);
   488 
   489         if (args.length < 3 || !args[args.length - 1].equals("--kill")) {
   490             System.out.println("Hit enter to stop it...");
   491             System.in.read();
   492         } else {
   493             synchronized (UI.class) {
   494                 UI.class.wait();
   495             }
   496         }
   497         r.call();
   498         System.exit(0);
   499     }
   500 
   501     static Callable<Void> startServers(int port, String remoteAPI, String remoteStatistics) throws Exception {
   502         Client client = new Client();
   503         Client client1 = new Client();
   504 
   505         final HttpServer apiServer;
   506         if (remoteAPI == null) {
   507             throw new IllegalArgumentException("Provide URL to API server"); // NOI18N
   508         } else {
   509             base = client.resource(new URI(remoteAPI));
   510             apiServer = null;
   511         }
   512 
   513         if (remoteStatistics != null) {
   514             stat = client1.resource(new URI(remoteStatistics));
   515         } else {
   516             stat = client1.resource(new URI("http://localhost:9444"));
   517         }
   518 
   519         ResourceConfig rc = new PackagesResourceConfig(
   520             "cz.xelfi.quoridor.freemarkerdor"
   521         );
   522 
   523         final String baseUri = "http://localhost:" + port + "/";
   524         final HttpServer server = HttpServerFactory.create(baseUri, rc);
   525         Client c3 = new Client();
   526         web = c3.resource(baseUri);
   527         server.start();
   528         System.out.println("Quoridor started at port " + port);
   529 
   530         return new Callable<Void>() {
   531             public Void call() throws Exception {
   532                 if (apiServer != null) {
   533                     apiServer.stop(0);
   534                 }
   535                 server.stop(0);
   536                 return null;
   537             }
   538         };
   539     }
   540 
   541     private ResourceBundle bundle(Locale[] locale) {
   542         ResourceBundle rb = null;
   543         String lng = user == null ? null : user.getProperty("language"); // NOI18N
   544         if (lng != null) {
   545             try {
   546                 Locale l = new Locale(lng);
   547                 rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", l);
   548                 if (locale != null) {
   549                     locale[0] = l;
   550                 }
   551             } catch (MissingResourceException e) {
   552                 // OK
   553             }
   554         }
   555         if (rb == null) {
   556             for (Locale l : headers.getAcceptableLanguages()) {
   557                 try {
   558                     rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", l);
   559                     if (locale != null) {
   560                         locale[0] = l;
   561                     }
   562                     break;
   563                 } catch (MissingResourceException e) {
   564                     // OK
   565                 }
   566             }
   567         }
   568         if (rb == null) {
   569             rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", Locale.ENGLISH);
   570             if (locale != null) {
   571                 locale[0] = Locale.ENGLISH;
   572             }
   573         }
   574         return rb;
   575     }
   576 
   577     private Viewable viewable(String page, Document doc, Object... more) {
   578         Locale[] locale = new Locale[1];
   579         ResourceBundle rb = bundle(locale);
   580 
   581         Map<String,Object> map = new HashMap<String,Object>();
   582         class ConvertToDate extends HashMap<Object,Object> {
   583             @Override
   584             public Object get(Object o) {
   585                 long time = Long.parseLong(o.toString());
   586                 return new Date(time);
   587             }
   588         }
   589 
   590         map.put("locale", locale[0].toString());
   591         map.put("doc", doc);
   592         if (user != null) {
   593             map.put("user", user.getId());
   594             map.put("email", user.getProperty("email"));
   595         }
   596         map.put("bundle", rb);
   597         map.put("toDate", new ConvertToDate());
   598         map.put("now", System.currentTimeMillis());
   599         map.put("version", version);
   600         for (int i = 0; i < more.length; i += 2) {
   601             map.put((String)more[i],more[i + 1]);
   602         }
   603         return new Viewable(page, map);
   604     }
   605 
   606 
   607     private static boolean isMobile(HttpHeaders headers) {
   608         final String[] keywords = {
   609             "Profile/MIDP",
   610         };
   611         List<String> agent = headers.getRequestHeader(HttpHeaders.USER_AGENT);
   612         if (agent != null) {
   613             for (String a : agent) {
   614                 for (String k : keywords) {
   615                     if (a.contains(k)) {
   616                         return true;
   617                     }
   618                 }
   619             }
   620         }
   621         return false;
   622     }
   623 
   624 }