Changing an email needs to be confirmed by receiving an email and responding to an URL send inside it
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
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]"
24 * Portions Copyrighted 2009 Jaroslav Tulach
27 package cz.xelfi.quoridor.freemarkerdor;
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;
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;
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;
74 * @author Jaroslav Tulach <jtulach@netbeans.org>
77 public final class UI {
78 private static final String version;
80 Properties p = new Properties();
82 InputStream is = FreemarkerProcessor.class.getResourceAsStream("/META-INF/maven/cz.xelfi.quoridor/freemarkerdor/pom.properties"); // NOI18N
86 } catch (IOException ex) {
89 version = p.getProperty("version", "unknown"); // NOI18N
91 private static WebResource base;
92 private static WebResource stat;
93 private static WebResource web;
94 private static Requests requests;
97 private HttpHeaders headers;
98 private UserInfo user;
104 private String login() {
105 Cookie cookie = headers.getCookies().get("login");
106 if (cookie != null) {
107 return cookie.getValue();
112 private Viewable checkLogin() {
117 us = base.path("users").queryParam("loginID", id).
118 accept(MediaType.TEXT_XML).get(UserInfo.class);
119 } catch (Exception ex) {
120 ex.printStackTrace();
123 if (us != null && us.getId().length() > 0) {
129 return viewable("login.fmt", null);
134 @Produces(MediaType.TEXT_HTML)
135 public Response login(
136 @FormParam("name") String name, @FormParam("password") String password
138 uuid = base.path("login").queryParam("name", name).queryParam("password", password).
139 accept(MediaType.TEXT_PLAIN).put(String.class);
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();
145 Viewable v = viewable("login.fmt", null, "message", "Invalid name or password: " + name);
146 return Response.status(1).entity(v).build();
151 @Produces(MediaType.TEXT_HTML)
152 public Response welcome(@QueryParam("maxItems") @DefaultValue("10") int maxItems) {
153 Viewable v = checkLogin();
154 ResponseBuilder resp = Response.ok();
156 v = welcomeImpl("maxItems", maxItems);
158 CacheControl cc = new CacheControl();
160 resp.cacheControl(cc);
161 return resp.entity(v).build();
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);
174 wr = wr.queryParam("move", "" + move);
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();
182 resp.cacheControl(cc);
183 return resp.entity(BoardImage.draw(b, fieldSize)).build();
187 private Response board(String id) {
188 return board(id, "", null);
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
198 return board(id, null, format, move);
200 private Response board(@PathParam("id") String id, String msg, String format, String m) {
201 Viewable v = checkLogin();
203 return Response.ok().entity(v).build();
207 move = Integer.parseInt(m);
208 } catch (NumberFormatException ex) {
211 WebResource url = base.path("games").queryParam("loginID", uuid).path(id);
213 url = url.queryParam("move", "" + move);
215 ResponseBuilder resp = Response.ok();
216 CacheControl cc = new CacheControl();
218 resp.cacheControl(cc);
219 Cookie cFormat = headers.getCookies().get("format");
220 if (format.length() == 0) {
221 if (cFormat != null) {
222 format = cFormat.getValue();
224 if (isMobile(headers)) {
229 if (cFormat == null || !format.equals(cFormat.getValue())) {
230 resp.cookie(new NewCookie("format", format));
234 Document doc = url.accept(MediaType.TEXT_XML).get(Document.class);
236 String t = doc.getElementsByTagName("board").item(0).getTextContent();
238 b = Board.valueOf(doc.getElementsByTagName("board").item(0).getTextContent());
239 } catch (Exception ex) {
241 // } catch (IllegalStateException ex) {
242 Exceptions.printStackTrace(ex);
247 bCode = stat.path("openings").path(b.getCode()+".check").queryParam("loginID", user.getId()).accept(MediaType.TEXT_PLAIN).get(String.class);
251 if(bCode == null || "".equals(bCode))
252 v = viewable("game.fmt", doc, "message", msg, "format", format, "board", b,"textPicture", boardToPicture(b));
254 v = viewable("game.fmt", doc, "message", msg, "format", format, "board", b,"textPicture", boardToPicture(b),"bCode", bCode);
255 return resp.entity(v).build();
258 private static String boardToPicture(Board b) {
259 StringWriter w = new StringWriter();
262 } catch (IOException ex) {
263 return ex.toString();
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
279 Viewable v = checkLogin();
281 return Response.ok().entity(v).build();
283 WebResource wr = base.path("games").path(id).
284 queryParam("loginID", uuid).
285 queryParam("player", user.getId());
287 if (type.equals("resign")) {
288 wr.queryParam("move", "RESIGN").put();
291 if (type.equals("fence")) {
292 wr.queryParam("move", direction.charAt(0) + column + row).put();
295 if (type.equals("move")) {
296 wr.queryParam("move", direction + directionNext).put();
299 } catch (UniformInterfaceException ex) {
300 return board(id, "WRONG_MOVE", "-1");
306 @Path("games/{id}/comment")
307 @Produces(MediaType.TEXT_HTML)
308 public Response comment(
309 @PathParam("id") String id,
310 @QueryParam("comment") String comment
312 Viewable v = checkLogin();
314 return Response.ok().entity(v).build();
316 WebResource wr = base.path("games").path(id).
317 queryParam("loginID", uuid).
318 queryParam("player", user.getId()).
319 queryParam("comment", comment);
326 @Path("games/create")
327 @Produces(MediaType.TEXT_HTML)
328 public Response create(
329 @QueryParam("white") String white,
330 @QueryParam("black") String black
332 Viewable v = checkLogin();
334 return Response.status(Response.Status.FORBIDDEN).entity(v).build();
337 if (user.getId().equals(white) || user.getId().equals(black)) {
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();
345 return Response.status(Response.Status.NOT_FOUND).
346 entity(welcomeImpl("message", "You (" + user.getId() + ") must be white or black!")).build();
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);
356 public Requests getRequests() {
357 if (requests == null) {
358 requests = new Requests(web.path("requests"));
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();
373 return Response.status(Response.Status.FORBIDDEN).entity(v).build();
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);
384 request = web.path("options").queryParam("name", "email").queryParam("email", email);
385 URI callback = getRequests().register(login(), request);
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();
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);
409 @Produces(MediaType.TEXT_HTML)
410 public Response getEloList(
411 @QueryParam("historyId") @DefaultValue("0") Integer historyId){
412 Viewable v = checkLogin();
414 return Response.status(Response.Status.FORBIDDEN).entity(v).build();
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();
422 @Produces(MediaType.TEXT_HTML)
423 public Response getOpeningRoot(){
424 return getOpeningNode("ROOT");
428 @Path("openings/{code}")
429 @Produces(MediaType.TEXT_HTML)
430 public Response getOpeningNode(@PathParam("code") String code){
431 Viewable v = checkLogin();
433 return Response.status(Response.Status.FORBIDDEN).entity(v).build();
435 final Document got = stat.path("openings").path(code).queryParam("loginID", user.getId()).accept(MediaType.TEXT_XML).get(Document.class);
438 b = Board.valueOf(got.getElementsByTagName("nodeCode").item(0).getTextContent());
439 } catch (Exception ex) {
440 Exceptions.printStackTrace(ex);
443 return Response.ok(viewable("openings.fmt", got, "whitefences",b.getPlayers().get(0).getFences(),"blackfences",b.getPlayers().get(1).getFences())).build();
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();
452 return Response.status(Response.Status.FORBIDDEN).entity(v).build();
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();
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();
469 resp.cacheControl(cc);
470 return resp.entity(BoardImage.draw(b, fieldSize)).build();
477 public static void main(String[] args) throws Exception {
479 if (args.length > 1) {
480 port = Integer.parseInt(args[0]);
482 String remoteAPI = args.length >= 2 ? args[1] : null;
483 String remoteStatistics = args.length >= 3 ? args[2] : null;
485 Locale.setDefault(Locale.ROOT);
487 Callable<Void> r = startServers(port, remoteAPI, remoteStatistics);
489 if (args.length < 3 || !args[args.length - 1].equals("--kill")) {
490 System.out.println("Hit enter to stop it...");
493 synchronized (UI.class) {
501 static Callable<Void> startServers(int port, String remoteAPI, String remoteStatistics) throws Exception {
502 Client client = new Client();
503 Client client1 = new Client();
505 final HttpServer apiServer;
506 if (remoteAPI == null) {
507 throw new IllegalArgumentException("Provide URL to API server"); // NOI18N
509 base = client.resource(new URI(remoteAPI));
513 if (remoteStatistics != null) {
514 stat = client1.resource(new URI(remoteStatistics));
516 stat = client1.resource(new URI("http://localhost:9444"));
519 ResourceConfig rc = new PackagesResourceConfig(
520 "cz.xelfi.quoridor.freemarkerdor"
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);
528 System.out.println("Quoridor started at port " + port);
530 return new Callable<Void>() {
531 public Void call() throws Exception {
532 if (apiServer != null) {
541 private ResourceBundle bundle(Locale[] locale) {
542 ResourceBundle rb = null;
543 String lng = user == null ? null : user.getProperty("language"); // NOI18N
546 Locale l = new Locale(lng);
547 rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", l);
548 if (locale != null) {
551 } catch (MissingResourceException e) {
556 for (Locale l : headers.getAcceptableLanguages()) {
558 rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", l);
559 if (locale != null) {
563 } catch (MissingResourceException e) {
569 rb = ResourceBundle.getBundle("cz.xelfi.quoridor.freemarkerdor.UI.Bundle", Locale.ENGLISH);
570 if (locale != null) {
571 locale[0] = Locale.ENGLISH;
577 private Viewable viewable(String page, Document doc, Object... more) {
578 Locale[] locale = new Locale[1];
579 ResourceBundle rb = bundle(locale);
581 Map<String,Object> map = new HashMap<String,Object>();
582 class ConvertToDate extends HashMap<Object,Object> {
584 public Object get(Object o) {
585 long time = Long.parseLong(o.toString());
586 return new Date(time);
590 map.put("locale", locale[0].toString());
593 map.put("user", user.getId());
594 map.put("email", user.getProperty("email"));
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]);
603 return new Viewable(page, map);
607 private static boolean isMobile(HttpHeaders headers) {
608 final String[] keywords = {
611 List<String> agent = headers.getRequestHeader(HttpHeaders.USER_AGENT);
613 for (String a : agent) {
614 for (String k : keywords) {