1 // 2 // SurveyForum.java 3 // 4 // Created by Steven R. Loomis on 27/10/2006. 5 // Copyright 2006-2013 IBM. All rights reserved. 6 // 7 8 package org.unicode.cldr.web; 9 10 import java.sql.Connection; 11 import java.sql.PreparedStatement; 12 import java.sql.ResultSet; 13 import java.sql.SQLException; 14 import java.sql.Statement; 15 import java.sql.Timestamp; 16 import java.util.Date; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Hashtable; 20 import java.util.Map; 21 import java.util.Set; 22 23 import org.json.JSONArray; 24 import org.json.JSONException; 25 import org.json.JSONObject; 26 import org.unicode.cldr.util.CLDRConfig; 27 import org.unicode.cldr.util.CLDRLocale; 28 import org.unicode.cldr.util.CLDRURLS; 29 import org.unicode.cldr.util.VoteResolver; 30 import org.unicode.cldr.web.SurveyException.ErrorCode; 31 import org.unicode.cldr.web.UserRegistry.User; 32 33 import com.ibm.icu.dev.util.ElapsedTimer; 34 import com.ibm.icu.text.DateFormat; 35 import com.ibm.icu.util.ULocale; 36 37 /** 38 * This class implements a discussion forum per language (ISO code) 39 */ 40 public class SurveyForum { 41 42 private static final String FLAGGED_FOR_REVIEW_HTML = " <p>[This item was flagged for CLDR TC review.]"; 43 44 private static java.util.logging.Logger logger; 45 46 private static String DB_FORA = "sf_fora"; // forum name -> id 47 48 private static String DB_LOC2FORUM = "sf_loc2forum"; // locale -> forum.. for selects. 49 50 private static final String F_FORUM = "forum"; 51 52 public static final String F_XPATH = "xpath"; 53 54 /** 55 * Make an "html-safe" version of the given string 56 * 57 * @param s 58 * @return the possibly-modified string 59 */ HTMLSafe(String s)60 public static String HTMLSafe(String s) { 61 if (s == null) { 62 return null; 63 } 64 return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); 65 } 66 67 /** 68 * Oops. HTML escaped into the DB 69 * 70 * @param s 71 * @return the possibly-modified string 72 */ HTMLUnsafe(String s)73 private static String HTMLUnsafe(String s) { 74 return s.replaceAll("<p>", "\n") 75 .replaceAll(""", "\"") 76 .replaceAll(">", ">") 77 .replaceAll("<", "<") 78 .replaceAll("&", "&"); 79 } 80 81 private Hashtable<String, Integer> nameToNum = new Hashtable<>(); 82 83 private static final int BAD_FORUM = -1; 84 private static final int NO_FORUM = -2; 85 86 /** 87 * A post with this value for "parent" is the first post in its 88 * thread; that is, it has no real parent post. 89 */ 90 public static final int NO_PARENT = -1; 91 getForumNumber(CLDRLocale locale)92 private synchronized int getForumNumber(CLDRLocale locale) { 93 String forum = localeToForum(locale); 94 if (forum.length() == 0) { 95 return NO_FORUM; // all forums 96 } 97 // make sure it is a valid src! 98 if ((forum == null) || (forum.indexOf('_') >= 0) || !sm.isValidLocale(CLDRLocale.getInstance(forum))) { 99 return BAD_FORUM; 100 } 101 Integer i = nameToNum.get(forum); 102 if (i == null) { 103 return createForum(forum); 104 } else { 105 return i.intValue(); 106 } 107 } 108 getForumNumberFromDB(String forum)109 private int getForumNumberFromDB(String forum) { 110 try { 111 Connection conn = null; 112 PreparedStatement fGetByLoc = null; 113 try { 114 conn = sm.dbUtils.getDBConnection(); 115 fGetByLoc = prepare_fGetByLoc(conn); 116 fGetByLoc.setString(1, forum); 117 ResultSet rs = fGetByLoc.executeQuery(); 118 if (!rs.next()) { 119 rs.close(); 120 return BAD_FORUM; 121 } else { 122 int j = rs.getInt(1); 123 rs.close(); 124 return j; 125 } 126 } finally { 127 DBUtils.close(fGetByLoc, conn); 128 } 129 } catch (SQLException se) { 130 String complaint = "SurveyForum: Couldn't add forum " + forum + " - " + DBUtils.unchainSqlException(se) 131 + " - fGetByLoc"; 132 logger.severe(complaint); 133 throw new RuntimeException(complaint); 134 } 135 } 136 137 /** 138 * 139 * @param forum 140 * @return the forum number 141 * 142 * Called only by getForumNumber. 143 */ createForum(String forum)144 private int createForum(String forum) { 145 int num = getForumNumberFromDB(forum); 146 if (num == BAD_FORUM) { 147 try { 148 Connection conn = null; 149 PreparedStatement fAdd = null; 150 try { 151 conn = sm.dbUtils.getDBConnection(); 152 fAdd = prepare_fAdd(conn); 153 fAdd.setString(1, forum); 154 fAdd.executeUpdate(); 155 conn.commit(); 156 } finally { 157 DBUtils.close(fAdd, conn); 158 } 159 } catch (SQLException se) { 160 String complaint = "SurveyForum: Couldn't add forum " + forum + " - " + DBUtils.unchainSqlException(se) 161 + " - fAdd"; 162 logger.severe(complaint); 163 throw new RuntimeException(complaint); 164 } 165 num = getForumNumberFromDB(forum); 166 } 167 168 if (num == BAD_FORUM) { 169 throw new RuntimeException("Couldn't query ID for forum " + forum); 170 } 171 // Add to list 172 Integer i = new Integer(num); 173 nameToNum.put(forum, i); 174 return num; 175 } 176 gatherInterestedUsers(String forum, Set<Integer> cc_emails, Set<Integer> bcc_emails)177 private int gatherInterestedUsers(String forum, Set<Integer> cc_emails, Set<Integer> bcc_emails) { 178 int emailCount = 0; 179 try { 180 Connection conn = null; 181 PreparedStatement pIntUsers = null; 182 try { 183 conn = sm.dbUtils.getDBConnection(); 184 pIntUsers = prepare_pIntUsers(conn); 185 pIntUsers.setString(1, forum); 186 187 ResultSet rs = pIntUsers.executeQuery(); 188 189 while (rs.next()) { 190 int uid = rs.getInt(1); 191 192 UserRegistry.User u = sm.reg.getInfo(uid); 193 if (u != null && u.email != null && u.email.length() > 0 194 && !(UserRegistry.userIsLocked(u) || UserRegistry.userIsExactlyAnonymous(u))) { 195 if (UserRegistry.userIsVetter(u)) { 196 cc_emails.add(u.id); 197 } else { 198 bcc_emails.add(u.id); 199 } 200 emailCount++; 201 } 202 } 203 } finally { 204 DBUtils.close(pIntUsers, conn); 205 } 206 } catch (SQLException se) { 207 String complaint = "SurveyForum: Couldn't gather interested users for " + forum + " - " 208 + DBUtils.unchainSqlException(se) + " - pIntUsers"; 209 logger.severe(complaint); 210 throw new RuntimeException(complaint); 211 } 212 213 return emailCount; 214 } 215 216 /** 217 * Send email notification to a set of users 218 * 219 * @param ctx 220 * @param forum 221 * @param base_xpath 222 * @param subj 223 * @param text 224 * @param postId 225 * 226 * Called by doPostInternal 227 */ emailNotify(UserRegistry.User user, CLDRLocale locale, int base_xpath, String subj, String text, Integer postId)228 private void emailNotify(UserRegistry.User user, CLDRLocale locale, int base_xpath, String subj, String text, Integer postId) { 229 String forum = localeToForum(locale); 230 ElapsedTimer et = new ElapsedTimer("Sending email to " + forum); 231 // Do email- 232 Set<Integer> cc_emails = new HashSet<>(); 233 Set<Integer> bcc_emails = new HashSet<>(); 234 235 // Collect list of users to send to. 236 gatherInterestedUsers(forum, cc_emails, bcc_emails); 237 238 String subject = "CLDR forum post (" + locale.getDisplayName() + " - " + locale + "): " + subj; 239 240 String body = "Do not reply to this message, instead go to <" 241 + CLDRConfig.getInstance().absoluteUrls().forSpecial(CLDRURLS.Special.Forum, locale, (String) null, Integer.toString(postId)) 242 243 + ">\n====\n\n" 244 + text; 245 246 if (MailSender.getInstance().DEBUG) { 247 System.out.println(et + ": Forum notify: u#" + user.id + " x" + base_xpath + " queueing cc:" + cc_emails.size() + " and bcc:" + bcc_emails.size()); 248 } 249 250 MailSender.getInstance().queue(user.id, cc_emails, bcc_emails, HTMLUnsafe(subject), HTMLUnsafe(body), locale, base_xpath, postId); 251 } 252 253 /** 254 * Is the current user allowed to post with the given PostType in this context? 255 * This was already checked on the client, but don't trust the client too much. 256 * Check on server as well, at least to prevent someone closing a post who shouldn't be allowed to. 257 * 258 * @param user the current user 259 * @param postType the PostType 260 * @param replyTo the post id of the parent, or NO_PARENT 261 * @return true or false 262 * 263 * @throws SurveyException 264 */ userCanUsePostType(User user, PostType postType, int replyTo)265 private boolean userCanUsePostType(User user, PostType postType, int replyTo) throws SurveyException { 266 if (SurveyMain.isPhaseReadonly()) { 267 return false; 268 } 269 if (postType != PostType.CLOSE) { 270 return true; 271 } 272 if (replyTo == NO_PARENT) { 273 return false; // first post can't begin as closed 274 } 275 if (getFirstPosterInThread(replyTo) == user.id) { 276 return true; 277 } 278 if (UserRegistry.userIsTC(user)) { 279 return true; 280 } 281 return false; 282 } 283 284 /** 285 * Get the user id of the first poster in the thread containing this post 286 * 287 * @param postId 288 * @return the user id, or UserRegistry.NO_USER 289 * 290 * @throws SurveyException 291 */ getFirstPosterInThread(int postId)292 private int getFirstPosterInThread(int postId) throws SurveyException { 293 int posterId = UserRegistry.NO_USER; 294 Connection conn = null; 295 PreparedStatement pList = null; 296 try { 297 conn = sm.dbUtils.getDBConnection(); 298 pList = DBUtils.prepareStatement(conn, "pList", "SELECT parent,poster FROM " + DBUtils.Table.FORUM_POSTS.toString() 299 + " WHERE id=?"); 300 for (;;) { 301 pList.setInt(1, postId); 302 ResultSet rs = pList.executeQuery(); 303 int parentId = NO_PARENT; 304 while (rs.next()) { 305 parentId = rs.getInt(1); 306 posterId = rs.getInt(2); 307 } 308 if (parentId == NO_PARENT) { 309 break; 310 } 311 postId = parentId; 312 } 313 } catch (SQLException se) { 314 String complaint = "SurveyForum: Couldn't get parent for post - " + DBUtils.unchainSqlException(se); 315 SurveyLog.logException(se, complaint); 316 throw new SurveyException(ErrorCode.E_INTERNAL, complaint); 317 } finally { 318 DBUtils.close(pList, conn); 319 } 320 return posterId; 321 } 322 323 /** 324 * If this user posts to the forum, will it cause this xpath+locale to be flagged 325 * (if not already flagged)? 326 * 327 * Return true if the user has made a losing vote, and the VoteResolver.canFlagOnLosing 328 * (i.e., path is locked and/or requires VoteResolver.HIGH_BAR votes). 329 * 330 * @param user 331 * @param xpath 332 * @param locale 333 * @return true or false 334 */ couldFlagOnLosing(UserRegistry.User user, String xpath, CLDRLocale locale)335 private boolean couldFlagOnLosing(UserRegistry.User user, String xpath, CLDRLocale locale) { 336 BallotBox<User> bb = sm.getSTFactory().ballotBoxForLocale(locale); 337 if (bb.userDidVote(user, xpath)) { 338 String userValue = bb.getVoteValue(user, xpath); 339 if (userValue != null) { 340 VoteResolver<String> vr = bb.getResolver(xpath); 341 if (!userValue.equals(vr.getWinningValue())) { 342 return vr.canFlagOnLosing(); 343 } 344 } 345 } 346 return false; 347 } 348 349 /** 350 * Called by SM to create the reg 351 * 352 * @param xlogger the logger to use 353 * @param ourConn the conn to use 354 * @return the SurveyForum 355 */ createTable(java.util.logging.Logger xlogger, Connection ourConn, SurveyMain sm)356 public static SurveyForum createTable(java.util.logging.Logger xlogger, Connection ourConn, SurveyMain sm) 357 throws SQLException { 358 SurveyForum reg = new SurveyForum(xlogger, sm); 359 try { 360 reg.setupDB(ourConn); // always call - we can figure it out. 361 } finally { 362 DBUtils.closeDBConnection(ourConn); 363 } 364 return reg; 365 } 366 SurveyForum(java.util.logging.Logger xlogger, SurveyMain ourSm)367 private SurveyForum(java.util.logging.Logger xlogger, SurveyMain ourSm) { 368 logger = xlogger; 369 sm = ourSm; 370 } 371 372 private Date oldOnOrBefore = null; 373 374 /** 375 * Re-create DB_LOC2FORUM table from scratch, called at start-up 376 * 377 * @param conn 378 * @throws SQLException 379 */ reloadLocales(Connection conn)380 private void reloadLocales(Connection conn) throws SQLException { 381 String sql = ""; 382 synchronized (conn) { 383 Statement s = conn.createStatement(); 384 if (!DBUtils.hasTable(conn, DB_LOC2FORUM)) { // user attribute 385 sql = "CREATE TABLE " + DB_LOC2FORUM + " ( " + " locale VARCHAR(255) NOT NULL, " 386 + " forum VARCHAR(255) NOT NULL" + " )"; 387 s.execute(sql); 388 sql = "CREATE UNIQUE INDEX " + DB_LOC2FORUM + "_loc ON " + DB_LOC2FORUM + " (locale) "; 389 s.execute(sql); 390 sql = "CREATE INDEX " + DB_LOC2FORUM + "_f ON " + DB_LOC2FORUM + " (forum) "; 391 s.execute(sql); 392 } else { 393 s.executeUpdate("delete from " + DB_LOC2FORUM); 394 } 395 s.close(); 396 397 PreparedStatement initbl = DBUtils.prepareStatement(conn, "initbl", "INSERT INTO " + DB_LOC2FORUM 398 + " (locale,forum) VALUES (?,?)"); 399 int errs = 0; 400 for (CLDRLocale l : SurveyMain.getLocalesSet()) { 401 initbl.setString(1, l.toString()); 402 String forum = localeToForum(l); 403 initbl.setString(2, forum); 404 try { 405 initbl.executeUpdate(); 406 } catch (SQLException se) { 407 if (errs == 0) { 408 System.err.println("While updating " + DB_LOC2FORUM + " - " + DBUtils.unchainSqlException(se) 409 + " - " + l + ":" + forum + ", [This and further errors, ignored]"); 410 } 411 errs++; 412 } 413 } 414 initbl.close(); 415 conn.commit(); 416 } 417 } 418 419 /** 420 * internal - called to setup db 421 */ setupDB(Connection conn)422 private void setupDB(Connection conn) throws SQLException { 423 String onOrBefore = CLDRConfig.getInstance().getProperty("CLDR_OLD_POSTS_BEFORE", "12/31/69"); 424 DateFormat sdf = DateFormat.getDateInstance(DateFormat.SHORT, ULocale.US); 425 try { 426 oldOnOrBefore = sdf.parse(onOrBefore); 427 } catch (Throwable t) { 428 System.err.println("Error in parsing CLDR_OLD_POSTS_BEFORE : " + onOrBefore + " - err " + t.toString()); 429 t.printStackTrace(); 430 oldOnOrBefore = null; 431 } 432 if (oldOnOrBefore == null) { 433 oldOnOrBefore = new Date(0); 434 } 435 System.err.println("CLDR_OLD_POSTS_BEFORE: date: " + sdf.format(oldOnOrBefore) + " (format: mm/dd/yy)"); 436 String sql = null; 437 String locindex = "loc"; 438 if (DBUtils.db_Mysql) { 439 locindex = "loc(122)"; 440 } 441 442 if (!DBUtils.hasTable(conn, DB_FORA)) { // user attribute 443 Statement s = conn.createStatement(); 444 sql = "CREATE TABLE " + DB_FORA + " ( " + " id INT NOT NULL " + DBUtils.DB_SQL_IDENTITY 445 + ", " 446 + " loc VARCHAR(122) NOT NULL, " 447 + // interest locale 448 " first_time " + DBUtils.DB_SQL_TIMESTAMP0 + " NOT NULL " + DBUtils.DB_SQL_WITHDEFAULT + " " 449 + DBUtils.DB_SQL_CURRENT_TIMESTAMP0 + ", " + " last_time TIMESTAMP NOT NULL " + DBUtils.DB_SQL_WITHDEFAULT 450 + " CURRENT_TIMESTAMP" + " )"; 451 s.execute(sql); 452 sql = ""; 453 s.close(); 454 conn.commit(); 455 } 456 if (!DBUtils.hasTable(conn, DBUtils.Table.FORUM_POSTS.toString())) { 457 Statement s = conn.createStatement(); 458 sql = "CREATE TABLE " + DBUtils.Table.FORUM_POSTS + " ( " + " id INT NOT NULL " 459 + DBUtils.DB_SQL_IDENTITY + ", " 460 + " forum INT NOT NULL, " // which forum (DB_FORA), i.e. de 461 + " poster INT NOT NULL, " + " subj " + DBUtils.DB_SQL_UNICODE + ", " + " text " + DBUtils.DB_SQL_UNICODE 462 + " NOT NULL, " + " parent INT " + DBUtils.DB_SQL_WITHDEFAULT 463 + " -1, " 464 + " loc VARCHAR(122), " // specific locale, i.e. de_CH 465 + " xpath INT, " // base xpath 466 + " last_time TIMESTAMP NOT NULL " + DBUtils.DB_SQL_WITHDEFAULT + " CURRENT_TIMESTAMP, " 467 + " version VARCHAR(122), " // CLDR version 468 + " root INT NOT NULL," 469 + " type INT NOT NULL," 470 + " is_open BOOLEAN NOT NULL," 471 + " value " + DBUtils.DB_SQL_UNICODE 472 + " )"; 473 s.execute(sql); 474 sql = "CREATE UNIQUE INDEX " + DBUtils.Table.FORUM_POSTS + "_id ON " + DBUtils.Table.FORUM_POSTS + " (id) "; 475 s.execute(sql); 476 sql = "CREATE INDEX " + DBUtils.Table.FORUM_POSTS + "_ut ON " + DBUtils.Table.FORUM_POSTS + " (poster, last_time) "; 477 s.execute(sql); 478 sql = "CREATE INDEX " + DBUtils.Table.FORUM_POSTS + "_utt ON " + DBUtils.Table.FORUM_POSTS + " (id, last_time) "; 479 s.execute(sql); 480 sql = "CREATE INDEX " + DBUtils.Table.FORUM_POSTS + "_chil ON " + DBUtils.Table.FORUM_POSTS + " (parent) "; 481 s.execute(sql); 482 sql = "CREATE INDEX " + DBUtils.Table.FORUM_POSTS + "_loc ON " + DBUtils.Table.FORUM_POSTS + " (" + locindex + ") "; 483 s.execute(sql); 484 sql = "CREATE INDEX " + DBUtils.Table.FORUM_POSTS + "_x ON " + DBUtils.Table.FORUM_POSTS + " (xpath) "; 485 s.execute(sql); 486 sql = ""; 487 s.close(); 488 conn.commit(); 489 } 490 reloadLocales(conn); 491 } 492 493 private SurveyMain sm = null; 494 prepare_fGetByLoc(Connection conn)495 private static PreparedStatement prepare_fGetByLoc(Connection conn) throws SQLException { 496 return DBUtils.prepareStatement(conn, "fGetByLoc", "SELECT id FROM " + DB_FORA + " where loc=?"); 497 } 498 prepare_fAdd(Connection conn)499 private static PreparedStatement prepare_fAdd(Connection conn) throws SQLException { 500 return DBUtils.prepareStatement(conn, "fAdd", "INSERT INTO " + DB_FORA + " (loc) values (?)"); 501 } 502 503 /** 504 * Prepare a statement for adding a new post to the forum table. 505 * 506 * @param conn the Connection 507 * @return the PreparedStatement 508 * @throws SQLException 509 * 510 * Called only by savePostToDb 511 */ prepare_pAdd(Connection conn)512 private static PreparedStatement prepare_pAdd(Connection conn) throws SQLException { 513 return DBUtils.prepareStatement(conn, "pAdd", "INSERT INTO " 514 + DBUtils.Table.FORUM_POSTS.toString() 515 + " (poster,subj,text,forum,parent,loc,xpath,version,root,type,is_open,value)" 516 + " values (?,?,?,?,?,?,?,?,?,?,?,?)"); 517 } 518 519 /** 520 * Prepare a statement for closing a thread of posts in the forum table. 521 * 522 * @param conn the Connection 523 * @return the PreparedStatement 524 * @throws SQLException 525 * 526 * Called only by savePostToDb 527 */ prepare_pCloseThread(Connection conn)528 private static PreparedStatement prepare_pCloseThread(Connection conn) throws SQLException { 529 return DBUtils.prepareStatement(conn, "pAdd", "UPDATE " 530 + DBUtils.Table.FORUM_POSTS.toString() 531 + " SET is_open=false WHERE id=? OR root=?"); 532 } 533 prepare_pIntUsers(Connection conn)534 private static PreparedStatement prepare_pIntUsers(Connection conn) throws SQLException { 535 return DBUtils.prepareStatement(conn, "pIntUsers", "SELECT uid from " + UserRegistry.CLDR_INTEREST + " where forum=?"); 536 } 537 localeToForum(ULocale locale)538 private static String localeToForum(ULocale locale) { 539 return locale.getLanguage(); 540 } 541 localeToForum(CLDRLocale locale)542 private static String localeToForum(CLDRLocale locale) { 543 return localeToForum(locale.toULocale()); 544 } 545 546 /** 547 * 548 * @param ctx 549 * @param forum 550 * @return 551 * 552 * Possibly called by tmpl/usermenu.jsp -- maybe dead code? 553 */ forumLink(WebContext ctx, String forum)554 public static String forumLink(WebContext ctx, String forum) { 555 String url = ctx.base() + "?" + F_FORUM + "=" + forum; 556 return "<a " + ctx.atarget(WebContext.TARGET_DOCS) + " class='forumlink' href='" + url + "' >" // title='"+title+"' 557 + "Forum" + "</a>"; 558 } 559 560 /** 561 * How many forum posts are there for the given locale and xpath? 562 * 563 * @param locale 564 * @param xpathId 565 * @return the number of posts 566 * 567 * Called by STFactory.PerLocaleData.voteForValue and SurveyAjax.processRequest (WHAT_FORUM_COUNT) 568 */ postCountFor(CLDRLocale locale, int xpathId)569 public int postCountFor(CLDRLocale locale, int xpathId) { 570 Connection conn = null; 571 PreparedStatement ps = null; 572 String tableName = DBUtils.Table.FORUM_POSTS.toString(); 573 try { 574 conn = DBUtils.getInstance().getDBConnection(); 575 576 ps = DBUtils.prepareForwardReadOnly(conn, "select count(*) from " + tableName + " where loc=? and xpath=?"); 577 ps.setString(1, locale.getBaseName()); 578 ps.setInt(2, xpathId); 579 580 return DBUtils.sqlCount(null, conn, ps); 581 } catch (SQLException e) { 582 SurveyLog.logException(e, "postCountFor for " + tableName + " " + locale + ":" + xpathId); 583 return 0; 584 } finally { 585 DBUtils.close(ps, conn); 586 } 587 } 588 589 /** 590 * Gather forum post information into a JSONArray, in preparation for 591 * displaying it to the user. 592 * 593 * @param session 594 * @param locale 595 * @param base_xpath Base XPath of the item being viewed, if positive; or XPathTable.NO_XPATH 596 * @param ident If nonzero - select only this item. If zero, select all items. 597 * @return the JSONArray 598 * @throws JSONException 599 * @throws SurveyException 600 */ toJSON(CookieSession session, CLDRLocale locale, int base_xpath, int ident)601 public JSONArray toJSON(CookieSession session, CLDRLocale locale, int base_xpath, int ident) throws JSONException, SurveyException { 602 assertCanAccessForum(session, locale); 603 604 JSONArray ret = new JSONArray(); 605 606 int forumNumber = getForumNumber(locale); 607 608 try { 609 Connection conn = null; 610 try { 611 conn = sm.dbUtils.getDBConnection(); 612 Object[][] o = null; 613 final String forumPosts = DBUtils.Table.FORUM_POSTS.toString(); 614 if (ident == 0) { 615 if (base_xpath == 0) { 616 // all posts 617 o = DBUtils.sqlQueryArrayArrayObj(conn, "select " + getPallresultfora(forumPosts) + " FROM " + forumPosts 618 + " WHERE (" + forumPosts + ".forum =? ) ORDER BY " 619 + forumPosts 620 + ".last_time DESC", forumNumber); 621 } else { 622 // all posts for xpath 623 o = DBUtils.sqlQueryArrayArrayObj(conn, "select " + getPallresultfora(forumPosts) + " FROM " + forumPosts 624 + " WHERE (" + forumPosts + ".forum =? AND " + forumPosts + " .xpath =? and " 625 + forumPosts + ".loc=? ) ORDER BY " 626 + forumPosts 627 + ".last_time DESC", forumNumber, base_xpath, locale); 628 } 629 } else { 630 // specific POST 631 if (base_xpath <= 0) { 632 o = DBUtils.sqlQueryArrayArrayObj(conn, "select " + getPallresultfora(forumPosts) + " FROM " + forumPosts 633 + " WHERE (" + forumPosts + ".forum =? AND " 634 + forumPosts + " .id =?) ORDER BY " 635 + forumPosts 636 + ".last_time DESC", 637 forumNumber, /* base_xpath,*/ident); 638 } else { 639 // just a restriction - specific post, specific xpath 640 o = DBUtils.sqlQueryArrayArrayObj(conn, "select " + getPallresultfora(forumPosts) + " FROM " + forumPosts 641 + " WHERE (" + forumPosts + ".forum =? AND " + forumPosts + " .xpath =? AND " 642 + forumPosts + " .id =?) ORDER BY " + forumPosts 643 + ".last_time DESC", forumNumber, base_xpath, ident); 644 } 645 } 646 if (o != null) { 647 for (int i = 0; i < o.length; i++) { 648 int poster = (Integer) o[i][0]; 649 String subj2 = (String) o[i][1]; 650 String text2 = (String) o[i][2]; 651 Timestamp lastDate = (Timestamp) o[i][3]; 652 int id = (Integer) o[i][4]; 653 int parent = (Integer) o[i][5]; 654 int xpath = (Integer) o[i][6]; 655 String loc = (String) o[i][7]; 656 String version = (String) o[i][8]; 657 int root = (int) o[i][9]; 658 int typeInt = (int) o[i][10]; 659 boolean open = (boolean) o[i][11]; 660 String value = (String) o[i][12]; 661 662 PostType type = PostType.fromInt(typeInt, PostType.DISCUSS); 663 664 if (lastDate.after(oldOnOrBefore)) { 665 JSONObject post = new JSONObject(); 666 post.put("poster", poster) 667 .put("subject", subj2) 668 .put("text", text2) 669 .put("postType", type.toName()) 670 .put("date", lastDate) 671 .put("date_long", lastDate.getTime()) 672 .put("id", id) 673 .put("parent", parent); 674 if (loc != null) { 675 post.put("locale", loc); 676 } 677 if (version != null) { 678 post.put("version", version); 679 } 680 if (value != null) { 681 post.put("value", value); 682 } 683 post.put("open", open); 684 post.put("root", root); 685 post.put("xpath_id", xpath); 686 if (xpath > 0) { 687 post.put("xpath", sm.xpt.getStringIDString(xpath)); 688 } 689 UserRegistry.User posterUser = sm.reg.getInfo(poster); 690 if (posterUser != null) { 691 JSONObject posterInfoJson = SurveyAjax.JSONWriter.wrap(posterUser); 692 if (posterInfoJson != null) { 693 post.put("posterInfo", posterInfoJson); 694 } 695 } 696 ret.put(post); 697 } 698 } 699 } 700 return ret; 701 } finally { 702 DBUtils.close(conn); 703 } 704 } catch (SQLException se) { 705 // When query fails, set breakpoint here and look at se.detailMessage for clues 706 String complaint = "SurveyForum: Couldn't show posts in forum " 707 + locale 708 + " - " + DBUtils.unchainSqlException(se) 709 + " - fGetByLoc"; 710 logger.severe(complaint); 711 throw new RuntimeException(complaint); 712 } 713 } 714 assertCanAccessForum(CookieSession session, CLDRLocale locale)715 private void assertCanAccessForum(CookieSession session, CLDRLocale locale) throws SurveyException { 716 if (session == null || session.user == null) { 717 throw new SurveyException(ErrorCode.E_NOT_LOGGED_IN); 718 } 719 assertCanAccessForum(session.user, locale); 720 } 721 assertCanAccessForum(UserRegistry.User user, CLDRLocale locale)722 private void assertCanAccessForum(UserRegistry.User user, CLDRLocale locale) throws SurveyException { 723 boolean canModify = (UserRegistry.userCanAccessForum(user, locale)); 724 if (!canModify) { 725 throw new SurveyException(ErrorCode.E_NO_PERMISSION, "You do not have permission to access that locale"); 726 } 727 } 728 729 /** 730 * Construct a portion of an sql query for getting all needed columns from the forum posts table. 731 * 732 * @param forumPosts the table name 733 * @return the string to be used as part of a query 734 */ getPallresultfora(String forumPosts)735 private static String getPallresultfora(String forumPosts) { 736 return forumPosts + ".poster," 737 + forumPosts + ".subj," 738 + forumPosts + ".text," 739 + forumPosts + ".last_time," 740 + forumPosts + ".id," 741 + forumPosts + ".parent," 742 + forumPosts + ".xpath, " 743 + forumPosts + ".loc," 744 + forumPosts + ".version," 745 + forumPosts + ".root," 746 + forumPosts + ".type," 747 + forumPosts + ".is_open," 748 + forumPosts + ".value"; 749 } 750 751 /** 752 * Respond when the user adds a new forum post. 753 * 754 * @param mySession the CookieSession 755 * @param xpath of the form "stringid" or "#1234" 756 * @param l the CLDRLocale 757 * @param subj the subject of the post 758 * @param text the text of the post 759 * @param postTypeStr the PostType string such as "Close", or null 760 * @param replyTo the id of the post to which this is a reply; {@link #NO_PARENT} if there is no parent 761 * @return the post id 762 * 763 * @throws SurveyException 764 */ doPost(CookieSession mySession, PostInfo postInfo)765 public int doPost(CookieSession mySession, PostInfo postInfo) throws SurveyException { 766 CLDRLocale locale = postInfo.getLocale(); 767 assertCanAccessForum(mySession, locale); 768 int replyTo = postInfo.getReplyTo(); 769 int base_xpath; 770 if (replyTo < 0) { 771 replyTo = NO_PARENT; 772 base_xpath = sm.xpt.getXpathIdOrNoneFromStringID(postInfo.getPathStr()); 773 } else { 774 base_xpath = DBUtils.sqlCount("select xpath from " + DBUtils.Table.FORUM_POSTS + " where id=?", replyTo); // default to -1 775 } 776 postInfo.setPath(base_xpath); 777 final boolean couldFlag = couldFlagOnLosing(postInfo.getUser(), sm.xpt.getById(base_xpath), locale) 778 && !sm.getSTFactory().getFlag(locale, base_xpath); 779 postInfo.setCouldFlagOnLosing(couldFlag); 780 if (couldFlag) { 781 postInfo.setText(postInfo.getText() + FLAGGED_FOR_REVIEW_HTML); 782 } 783 return doPostInternal(postInfo); 784 } 785 786 /** 787 * Update the forum as appropriate after a vote has been accepted 788 * 789 * @param locale 790 * @param user 791 * @param distinguishingXpath 792 * @param xpathId 793 * @param value 794 * @param didClearFlag 795 */ doForumAfterVote(CLDRLocale locale, User user, String distinguishingXpath, int xpathId, String value, boolean didClearFlag)796 public void doForumAfterVote(CLDRLocale locale, User user, String distinguishingXpath, int xpathId, 797 String value, boolean didClearFlag) { 798 if (didClearFlag) { 799 try { 800 int newPostId = postFlagRemoved(xpathId, locale, user); 801 System.out.println("NOTE: flag was removed from " 802 + locale + " " + distinguishingXpath + " - post ID=" + newPostId 803 + " by " + user.toString()); 804 } catch (SurveyException e) { 805 SurveyLog.logException(e, "Error trying to post that a flag was removed from " 806 + locale + " " + distinguishingXpath); 807 } 808 } 809 final boolean ENABLE_AUTO_POSTING = true; 810 if (ENABLE_AUTO_POSTING) { 811 if (value != null) { 812 autoPostAgree(locale, user, xpathId, value); 813 } 814 autoPostDecline(locale, user, xpathId, value); 815 autoPostClose(locale, user, xpathId, value); 816 } 817 } 818 819 /** 820 * Make a special post for flag removal 821 * 822 * @param xpathId 823 * @param locale 824 * @param user 825 * @return the post id 826 * 827 * @throws SurveyException 828 */ postFlagRemoved(int xpathId, CLDRLocale locale, User user)829 private int postFlagRemoved(int xpathId, CLDRLocale locale, User user) throws SurveyException { 830 PostInfo postInfo = new PostInfo(locale, PostType.CLOSE.toName(), "(The flag was removed.)"); 831 postInfo.setSubj("Flag Removed"); 832 postInfo.setPath(xpathId); 833 postInfo.setUser(user); 834 return doPostInternal(postInfo); 835 } 836 837 /** 838 * Auto-post Agree for each open Request post for this locale+path+value by other users 839 * 840 * @param locale 841 * @param user 842 * @param xpathId 843 * @param value 844 */ autoPostAgree(CLDRLocale locale, User user, int xpathId, String value)845 private void autoPostAgree(CLDRLocale locale, User user, int xpathId, String value) { 846 Connection conn = null; 847 PreparedStatement pList = null; 848 String tableName = DBUtils.Table.FORUM_POSTS.toString(); 849 Map<Integer, String> posts = new HashMap<>(); 850 try { 851 conn = sm.dbUtils.getDBConnection(); 852 pList = DBUtils.prepareStatement(conn, "pList", 853 "SELECT id,subj FROM " + tableName 854 + " WHERE is_open=true AND type=? AND loc=? AND xpath=? AND value=? AND NOT poster=?"); 855 pList.setInt(1, PostType.REQUEST.toInt()); 856 pList.setString(2, locale.toString()); 857 pList.setInt(3, xpathId); 858 DBUtils.setStringUTF8(pList, 4, value); 859 pList.setInt(5, user.id); 860 ResultSet rs = pList.executeQuery(); 861 while (rs.next()) { 862 posts.put(rs.getInt(1), DBUtils.getStringUTF8(rs, 2)); 863 } 864 posts.forEach((root, subject) -> autoPostReplyAgree(root, subject, locale, user, xpathId, value)); 865 } catch (SQLException se) { 866 String complaint = "SurveyForum: autoPostAgree - " + DBUtils.unchainSqlException(se); 867 SurveyLog.logException(se, complaint); 868 } finally { 869 DBUtils.close(pList, conn); 870 } 871 } 872 autoPostReplyAgree(int root, String subject, CLDRLocale locale, User user, int xpathId, String value)873 private void autoPostReplyAgree(int root, String subject, CLDRLocale locale, User user, 874 int xpathId, String value) { 875 String text = "(Auto-generated:) I voted for “" + value + "”"; 876 PostInfo postInfo = new PostInfo(locale, PostType.AGREE.toName(), text); 877 postInfo.setSubj(subject); 878 postInfo.setPath(xpathId); 879 postInfo.setUser(user); 880 postInfo.setReplyTo(root /* replyTo */); 881 postInfo.setRoot(root); 882 postInfo.setValue(value); 883 postInfo.setSendEmail(false); 884 try { 885 doPostInternal(postInfo); 886 } catch (SurveyException e) { 887 SurveyLog.logException(e, "SurveyForum: autoPostReplyAgree root " + root); 888 } 889 } 890 891 /** 892 * For each open AGREE post by this user, for this locale+path, where 893 * the given value is NOT the same as the requested value, generate a new DECLINE post. 894 * 895 * @param locale 896 * @param user 897 * @param xpathId 898 * @param value 899 */ autoPostDecline(CLDRLocale locale, User user, int xpathId, String value)900 private void autoPostDecline(CLDRLocale locale, User user, int xpathId, String value) { 901 String dbValue = value == null ? "" : value; 902 Connection conn = null; 903 PreparedStatement pList = null; 904 String tableName = DBUtils.Table.FORUM_POSTS.toString(); 905 Map<Integer, String> posts = new HashMap<>(); 906 try { 907 conn = sm.dbUtils.getDBConnection(); 908 pList = DBUtils.prepareStatement(conn, "pList", 909 "SELECT root,subj FROM " + tableName 910 + " WHERE is_open=true AND type=? AND loc=? AND xpath=? AND poster=? AND NOT value=?"); 911 pList.setInt(1, PostType.AGREE.toInt()); 912 pList.setString(2, locale.toString()); 913 pList.setInt(3, xpathId); 914 pList.setInt(4, user.id); 915 DBUtils.setStringUTF8(pList, 5, dbValue); 916 ResultSet rs = pList.executeQuery(); 917 while (rs.next()) { 918 posts.put(rs.getInt(1), DBUtils.getStringUTF8(rs, 2)); 919 } 920 posts.forEach((root, subject) -> autoPostReplyDecline(root, subject, locale, user, xpathId, value)); 921 } catch (SQLException se) { 922 String complaint = "SurveyForum: autoPostDecline - " + DBUtils.unchainSqlException(se); 923 SurveyLog.logException(se, complaint); 924 } finally { 925 DBUtils.close(pList, conn); 926 } 927 } 928 autoPostReplyDecline(int root, String subject, CLDRLocale locale, User user, int xpathId, String value)929 private void autoPostReplyDecline(int root, String subject, CLDRLocale locale, User user, 930 int xpathId, String value) { 931 String text = (value == null) 932 ? "(Auto-generated:) I changed my vote to Abstain, and will reconsider my vote." 933 : "(Auto-generated:) I changed my vote to “" + value + "”, which now disagrees with the request."; 934 PostInfo postInfo = new PostInfo(locale, PostType.DECLINE.toName(), text); 935 postInfo.setSubj(subject); 936 postInfo.setPath(xpathId); 937 postInfo.setUser(user); 938 postInfo.setReplyTo(root /* replyTo */); 939 postInfo.setRoot(root); 940 postInfo.setValue(value == null ? "Abstain" : value); /* NOT the same as the requested value */ 941 try { 942 doPostInternal(postInfo); 943 } catch (SurveyException e) { 944 SurveyLog.logException(e, "SurveyForum: autoPostReplyDecline root " + root); 945 } 946 } 947 948 /** 949 * I request a vote for “X”, then I change to “Y” (or abstain) 950 * Auto-generated: I changed my vote to “Y”, disagreeing with my request. This topic is being closed. ⇒ Close 951 * 952 * @param locale 953 * @param user 954 * @param xpathId 955 * @param value 956 */ autoPostClose(CLDRLocale locale, User user, int xpathId, String value)957 private void autoPostClose(CLDRLocale locale, User user, int xpathId, String value) { 958 String dbValue = value == null ? "" : value; 959 Connection conn = null; 960 PreparedStatement pList = null; 961 String tableName = DBUtils.Table.FORUM_POSTS.toString(); 962 Map<Integer, String> posts = new HashMap<>(); 963 try { 964 conn = sm.dbUtils.getDBConnection(); 965 pList = DBUtils.prepareStatement(conn, "pList", 966 "SELECT id,subj FROM " + tableName 967 + " WHERE is_open=true AND type=? AND loc=? AND xpath=? AND poster=? AND NOT value=?"); 968 pList.setInt(1, PostType.REQUEST.toInt()); 969 pList.setString(2, locale.toString()); 970 pList.setInt(3, xpathId); 971 pList.setInt(4, user.id); 972 DBUtils.setStringUTF8(pList, 5, dbValue); 973 ResultSet rs = pList.executeQuery(); 974 while (rs.next()) { 975 posts.put(rs.getInt(1), DBUtils.getStringUTF8(rs, 2)); 976 } 977 posts.forEach((root, subject) -> autoPostReplyClose(root, subject, locale, user, xpathId, value)); 978 } catch (SQLException se) { 979 String complaint = "SurveyForum: autoPostClose - " + DBUtils.unchainSqlException(se); 980 SurveyLog.logException(se, complaint); 981 } finally { 982 DBUtils.close(pList, conn); 983 } 984 } 985 autoPostReplyClose(int root, String subject, CLDRLocale locale, User user, int xpathId, String value)986 private void autoPostReplyClose(int root, String subject, CLDRLocale locale, User user, 987 int xpathId, String value) { 988 String abstainOrQuotedValue = value == null ? "Abstain" : "“" + value + "”"; 989 String text = "(Auto-generated:) I changed my vote to " + abstainOrQuotedValue 990 + ", disagreeing with my request. This topic is being closed."; 991 PostInfo postInfo = new PostInfo(locale, PostType.CLOSE.toName(), text); 992 postInfo.setSubj(subject); 993 postInfo.setPath(xpathId); 994 postInfo.setUser(user); 995 postInfo.setReplyTo(root /* replyTo */); 996 postInfo.setRoot(root); 997 postInfo.setValue(value == null ? "Abstain" : value); /* NOT the same as the requested value */ 998 postInfo.setSendEmail(false); 999 try { 1000 doPostInternal(postInfo); 1001 } catch (SurveyException e) { 1002 SurveyLog.logException(e, "SurveyForum: autoPostReplyClose root " + root); 1003 } 1004 } 1005 1006 /** 1007 * Respond to the user making a new forum post. Save the post in the database, 1008 * and send an email if appropriate. 1009 * 1010 * @param postInfo the post info 1011 * 1012 * @return the new post id, or <= 0 for failure 1013 * @throws SurveyException 1014 */ doPostInternal(PostInfo postInfo)1015 private Integer doPostInternal(PostInfo postInfo) throws SurveyException { 1016 if (!postInfo.isValid()) { 1017 SurveyLog.errln("Invalid postInfo in SurveyForum.doPostInternal"); 1018 return 0; 1019 } 1020 PostType postType = postInfo.getType(); 1021 User user = postInfo.getUser(); 1022 if (!userCanUsePostType(user, postType, postInfo.getReplyTo())) { 1023 SurveyLog.errln("Post not allowed in SurveyForum.doPostInternal"); 1024 return 0; 1025 } 1026 int postId = savePostToDb(postInfo); 1027 1028 if (postInfo.getSendEmail()) { 1029 emailNotify(user, postInfo.getLocale(), postInfo.getPath(), postInfo.getSubj(), postInfo.getText(), postId); 1030 } 1031 return postId; 1032 } 1033 1034 /** 1035 * Save a new post to the FORUM_POSTS table; if it's a CLOSE post, 1036 * also set is_open=false for all posts in this thread 1037 * 1038 * @param PostInfo the post info 1039 * @return the new post id, or <= 0 for failure 1040 * @throws SurveyException 1041 */ savePostToDb(PostInfo postInfo)1042 private int savePostToDb(PostInfo postInfo) throws SurveyException { 1043 int postId = 0; 1044 final CLDRLocale locale = postInfo.getLocale(); 1045 final String localeStr = locale.toString(); 1046 final int forumNumber = getForumNumber(locale); 1047 final User user = postInfo.getUser(); 1048 final PostType type = postInfo.getType(); 1049 final boolean open = (type == PostType.CLOSE) ? false : postInfo.getOpen(); 1050 final int root = postInfo.getRoot(); 1051 final String text = postInfo.getText().replaceAll("\r", "").replaceAll("\n", "<p>"); 1052 try { 1053 Connection conn = null; 1054 PreparedStatement pAdd = null, pCloseThread = null; 1055 try { 1056 conn = sm.dbUtils.getDBConnection(); 1057 if (type == PostType.CLOSE) { 1058 pCloseThread = prepare_pCloseThread(conn); 1059 pCloseThread.setInt(1, root); 1060 pCloseThread.setInt(2, root); 1061 pCloseThread.executeUpdate(); 1062 } 1063 pAdd = prepare_pAdd(conn); 1064 pAdd.setInt(1, user.id); 1065 DBUtils.setStringUTF8(pAdd, 2, postInfo.getSubj()); 1066 DBUtils.setStringUTF8(pAdd, 3, text); 1067 pAdd.setInt(4, forumNumber); 1068 pAdd.setInt(5, postInfo.getReplyTo()); // record parent 1069 pAdd.setString(6, localeStr); // real locale of item, not forum # 1070 pAdd.setInt(7, postInfo.getPath()); 1071 pAdd.setString(8, SurveyMain.getNewVersion()); // version 1072 pAdd.setInt(9, root); 1073 pAdd.setInt(10, type.toInt()); 1074 pAdd.setBoolean(11, open); 1075 DBUtils.setStringUTF8(pAdd, 12, postInfo.getValue()); 1076 1077 int n = pAdd.executeUpdate(); 1078 if (postInfo.couldFlagOnLosing()) { 1079 sm.getSTFactory().setFlag(conn, locale, postInfo.getPath(), user); 1080 System.out.println("NOTE: flag was set on " + localeStr + " by " + user.toString()); 1081 } 1082 1083 conn.commit(); 1084 postId = DBUtils.getLastId(pAdd); 1085 1086 if (n != 1) { 1087 throw new RuntimeException("Couldn't post to " + localeStr + " - update failed."); 1088 } 1089 } finally { 1090 DBUtils.close(pAdd, pCloseThread, conn); 1091 } 1092 } catch (SQLException se) { 1093 String complaint = "SurveyForum: Couldn't add post to " + localeStr + " - " + DBUtils.unchainSqlException(se) 1094 + " - pAdd"; 1095 SurveyLog.logException(se, complaint); 1096 throw new SurveyException(ErrorCode.E_INTERNAL, complaint); 1097 } 1098 return postId; 1099 } 1100 1101 public class PostInfo { 1102 private final CLDRLocale locale; 1103 private PostType type; 1104 private String text; 1105 private int xpathId = XPathTable.NO_XPATH; 1106 private String pathString = null; 1107 private int replyTo = NO_PARENT; 1108 private int root = NO_PARENT; 1109 private String subj = null; 1110 private String value = null; 1111 private boolean open = true; 1112 private boolean couldFlag = false; 1113 private UserRegistry.User user = null; 1114 private boolean sendEmail = true; 1115 PostInfo(CLDRLocale locale, String postTypeStr, String text)1116 public PostInfo(CLDRLocale locale, String postTypeStr, String text) { 1117 this.locale = locale; 1118 this.type = PostType.fromName(postTypeStr, null); 1119 this.text = text; 1120 } 1121 isValid()1122 public boolean isValid() { 1123 if (locale == null 1124 || type == null 1125 || text == null 1126 || subj == null 1127 || user == null) { 1128 return false; 1129 } 1130 if ((replyTo == NO_PARENT) != (root == NO_PARENT)) { 1131 return false; 1132 } 1133 if (value == null && (type == PostType.REQUEST || type == PostType.AGREE || type == PostType.DECLINE)) { 1134 return false; 1135 } 1136 return true; 1137 } 1138 1139 /* 1140 * Getters 1141 */ 1142 getPathStr()1143 public String getPathStr() { 1144 return pathString; 1145 } 1146 getValue()1147 public String getValue() { 1148 return value; 1149 } 1150 getOpen()1151 public boolean getOpen() { 1152 return open; 1153 } 1154 getType()1155 public PostType getType() { 1156 return type; 1157 } 1158 getRoot()1159 public int getRoot() { 1160 return root; 1161 } 1162 getLocale()1163 public CLDRLocale getLocale() { 1164 return locale; 1165 } 1166 couldFlagOnLosing()1167 public boolean couldFlagOnLosing() { 1168 return couldFlag; 1169 } 1170 getPath()1171 public int getPath() { 1172 return xpathId; 1173 } 1174 getText()1175 public String getText() { 1176 return text; 1177 } 1178 getSubj()1179 public String getSubj() { 1180 return subj; 1181 } 1182 getReplyTo()1183 public int getReplyTo() { 1184 return replyTo; 1185 } 1186 getUser()1187 public User getUser() { 1188 return user; 1189 } 1190 getSendEmail()1191 public boolean getSendEmail() { 1192 return sendEmail; 1193 } 1194 1195 /* 1196 * Setters 1197 */ 1198 setRoot(int root)1199 public void setRoot(int root) { 1200 this.root = root; 1201 } 1202 setSubj(String subj)1203 public void setSubj(String subj) { 1204 this.subj = subj; 1205 } 1206 setPathString(String xpathStr)1207 public void setPathString(String xpathStr) { 1208 this.pathString = xpathStr; 1209 } 1210 setReplyTo(int replyTo)1211 public void setReplyTo(int replyTo) { 1212 this.replyTo = replyTo; 1213 } 1214 setUser(User user)1215 public void setUser(User user) { 1216 this.user = user; 1217 } 1218 setPath(int base_xpath)1219 public void setPath(int base_xpath) { 1220 this.xpathId = base_xpath; 1221 } 1222 setCouldFlagOnLosing(boolean couldFlag)1223 public void setCouldFlagOnLosing(boolean couldFlag) { 1224 this.couldFlag = couldFlag; 1225 } 1226 setText(String text)1227 public void setText(String text) { 1228 this.text = text; 1229 } 1230 setOpen(boolean open)1231 public void setOpen(boolean open) { 1232 this.open = open; 1233 } 1234 setValue(String value)1235 public void setValue(String value) { 1236 this.value = value; 1237 } 1238 setSendEmail(boolean sendEmail)1239 public void setSendEmail(boolean sendEmail) { 1240 this.sendEmail = sendEmail; 1241 } 1242 } 1243 1244 /** 1245 * Status values associated with forum posts and threads 1246 */ 1247 enum PostType { 1248 CLOSE(0, "Close"), 1249 DISCUSS(1, "Discuss"), 1250 REQUEST(2, "Request"), 1251 AGREE(3, "Agree"), 1252 DECLINE(4, "Decline"); 1253 PostType(int id, String name)1254 PostType(int id, String name) { 1255 this.id = id; 1256 this.name = name; 1257 } 1258 1259 private final int id; 1260 private final String name; 1261 1262 /** 1263 * Get the integer id for this PostType 1264 * 1265 * @return the id 1266 */ toInt()1267 public int toInt() { 1268 return id; 1269 } 1270 1271 /** 1272 * Get the name for this PostType 1273 * 1274 * @return the name 1275 */ toName()1276 public String toName() { 1277 return name; 1278 } 1279 1280 /** 1281 * Get a PostType value from its name, or if the name is not associated with 1282 * a PostType value, use the given default PostType 1283 * 1284 * @param i 1285 * @param defaultStatus 1286 * @return the PostType 1287 */ fromInt(int i, PostType defaultStatus)1288 public static PostType fromInt(int i, PostType defaultStatus) { 1289 for (PostType s : PostType.values()) { 1290 if (s.id == i) { 1291 return s; 1292 } 1293 } 1294 return defaultStatus; 1295 } 1296 1297 /** 1298 * Get a PostType value from its name, or if the name is not associated with 1299 * a PostType value, use the given default PostType 1300 * 1301 * @param name 1302 * @param defaultStatus 1303 * @return the PostType 1304 */ fromName(String name, PostType defaultStatus)1305 public static PostType fromName(String name, PostType defaultStatus) { 1306 if (name != null) { 1307 for (PostType s : PostType.values()) { 1308 if (s.name.equals(name)) { 1309 return s; 1310 } 1311 } 1312 } 1313 return defaultStatus; 1314 } 1315 } 1316 } 1317