1 /* 2 ****************************************************************************** 3 * Copyright (C) 2004-2014, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ****************************************************************************** 6 */ 7 package org.unicode.cldr.web; 8 9 import java.io.BufferedReader; 10 import java.io.Externalizable; 11 import java.io.File; 12 import java.io.FileFilter; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.io.ObjectInput; 16 import java.io.ObjectOutput; 17 import java.io.PrintStream; 18 import java.io.PrintWriter; 19 import java.io.StringWriter; 20 import java.lang.management.ManagementFactory; 21 import java.lang.management.OperatingSystemMXBean; 22 import java.net.InetAddress; 23 import java.net.URLEncoder; 24 import java.sql.Connection; 25 import java.sql.PreparedStatement; 26 import java.sql.ResultSet; 27 import java.sql.ResultSetMetaData; 28 import java.sql.SQLException; 29 import java.sql.Statement; 30 import java.sql.Timestamp; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.Date; 34 import java.util.Enumeration; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.Hashtable; 38 import java.util.Iterator; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Properties; 42 import java.util.Set; 43 import java.util.TreeMap; 44 import java.util.TreeSet; 45 import java.util.concurrent.Executors; 46 import java.util.concurrent.ScheduledExecutorService; 47 import java.util.concurrent.ScheduledFuture; 48 import java.util.concurrent.TimeUnit; 49 import java.util.jar.JarFile; 50 import java.util.jar.Manifest; 51 import java.util.regex.Pattern; 52 53 import javax.servlet.ServletConfig; 54 import javax.servlet.ServletException; 55 import javax.servlet.ServletOutputStream; 56 import javax.servlet.http.HttpServlet; 57 import javax.servlet.http.HttpServletRequest; 58 import javax.servlet.http.HttpServletResponse; 59 import javax.servlet.jsp.JspWriter; 60 61 import org.json.JSONArray; 62 import org.json.JSONException; 63 import org.json.JSONObject; 64 import org.unicode.cldr.draft.FileUtilities; 65 import org.unicode.cldr.test.CheckCLDR; 66 import org.unicode.cldr.test.ExampleGenerator; 67 import org.unicode.cldr.test.HelpMessages; 68 import org.unicode.cldr.util.CLDRConfig; 69 import org.unicode.cldr.util.CLDRConfigImpl; 70 import org.unicode.cldr.util.CLDRFile; 71 import org.unicode.cldr.util.CLDRLocale; 72 import org.unicode.cldr.util.CLDRLocale.CLDRFormatter; 73 import org.unicode.cldr.util.CLDRLocale.FormatBehavior; 74 import org.unicode.cldr.util.CLDRURLS; 75 import org.unicode.cldr.util.CldrUtility; 76 import org.unicode.cldr.util.CoverageInfo; 77 import org.unicode.cldr.util.Factory; 78 import org.unicode.cldr.util.Factory.DirectoryType; 79 import org.unicode.cldr.util.Factory.SourceTreeType; 80 import org.unicode.cldr.util.LDMLUtilities; 81 import org.unicode.cldr.util.Level; 82 import org.unicode.cldr.util.Organization; 83 import org.unicode.cldr.util.Pair; 84 import org.unicode.cldr.util.PathHeader; 85 import org.unicode.cldr.util.PathHeader.PageId; 86 import org.unicode.cldr.util.PathHeader.SurveyToolStatus; 87 import org.unicode.cldr.util.SimpleFactory; 88 import org.unicode.cldr.util.SpecialLocales; 89 import org.unicode.cldr.util.SpecialLocales.Type; 90 import org.unicode.cldr.util.StackTracker; 91 import org.unicode.cldr.util.StandardCodes; 92 import org.unicode.cldr.util.SupplementalDataInfo; 93 import org.unicode.cldr.util.TransliteratorUtilities; 94 import org.unicode.cldr.util.VoteResolver; 95 import org.unicode.cldr.util.XMLSource; 96 import org.unicode.cldr.web.SurveyAjax.AjaxType; 97 import org.unicode.cldr.web.UserRegistry.InfoType; 98 import org.unicode.cldr.web.WebContext.HTMLDirection; 99 import org.w3c.dom.Document; 100 import org.w3c.dom.Node; 101 102 import com.ibm.icu.dev.util.ElapsedTimer; 103 import com.ibm.icu.lang.UCharacter; 104 import com.ibm.icu.text.ListFormatter; 105 import com.ibm.icu.text.SimpleDateFormat; 106 import com.ibm.icu.text.UnicodeSet; 107 import com.ibm.icu.util.Calendar; 108 import com.ibm.icu.util.TimeZone; 109 import com.ibm.icu.util.ULocale; 110 111 /** 112 * The main servlet class of Survey Tool 113 */ 114 public class SurveyMain extends HttpServlet implements CLDRProgressIndicator, Externalizable { 115 116 private static final String VURL_LOCALES = "v#locales///"; 117 public static final String CLDR_OLDVERSION = "CLDR_OLDVERSION"; 118 public static final String CLDR_NEWVERSION = "CLDR_NEWVERSION"; 119 public static final String CLDR_LASTVOTEVERSION = "CLDR_LASTVOTEVERSION"; 120 public static final String CLDR_DIR = "CLDR_DIR"; 121 private static final String CLDR_DIR_REPOS = "http://unicode.org/repos/cldr"; 122 123 private static final String NEWVERSION_EPOCH = "1970-01-01 00:00:00"; 124 125 private static final String CLDR_NEWVERSION_AFTER = "CLDR_NEWVERSION_AFTER"; 126 127 public static Stamp surveyRunningStamp = Stamp.getInstance(); 128 129 public static final String QUERY_SAVE_COOKIE = "save_cookie"; 130 131 /** 132 * The "r_" prefix is for r_vetting_json.jsp (Dashboard); 133 * also "r_datetime", "r_zones", and "r_compact" -- see ReportMenu. 134 */ 135 private static final String REPORT_PREFIX = "r_"; 136 137 /** 138 * r_vetting_json.jsp is for the Dashboard 139 */ 140 public static final String R_VETTING_JSON = REPORT_PREFIX + "vetting_json"; // r_vetting_json 141 142 private static final String XML_CACHE_PROPERTIES = "xmlCache.properties"; 143 private static UnicodeSet supportedNameSet = new UnicodeSet("[a-zA-Z]").freeze(); 144 static final int TWELVE_WEEKS = 3600 * 24 * 7 * 12; 145 146 public static final String DEFAULT_CONTENT_LINK = "<i><a target='CLDR-ST-DOCS' href='http://cldr.unicode.org/translation/default-content'>default content locale</a></i>"; 147 148 /** 149 * 150 */ 151 private static final long serialVersionUID = -3587451989643792204L; 152 153 /** 154 * This class enumerates the current phase of the survey tool 155 * 156 * @author srl 157 * 158 */ 159 public enum Phase { 160 SUBMIT("Data Submission", CheckCLDR.Phase.SUBMISSION), // SUBMISSION 161 VETTING("Vetting", CheckCLDR.Phase.VETTING), VETTING_CLOSED("Vetting Closed", CheckCLDR.Phase.FINAL_TESTING), // closed 162 // after 163 // vetting 164 // - 165 // open 166 // for 167 // admin 168 CLOSED("Closed", CheckCLDR.Phase.FINAL_TESTING), // closed 169 DISPUTED("Dispute Resolution", CheckCLDR.Phase.VETTING), FINAL_TESTING("Final Testing", CheckCLDR.Phase.FINAL_TESTING), // FINAL_TESTING 170 READONLY("Read-Only", CheckCLDR.Phase.FINAL_TESTING), BETA("Beta", CheckCLDR.Phase.SUBMISSION); 171 172 private String what; 173 private CheckCLDR.Phase cphase; 174 Phase(String s, CheckCLDR.Phase ph)175 private Phase(String s, CheckCLDR.Phase ph) { 176 what = s; 177 this.cphase = ph; 178 } 179 180 @Override toString()181 public String toString() { 182 return what; 183 } 184 185 /** 186 * Get the CheckCLDR.Phase equivalent 187 * 188 * @return 189 */ getCPhase()190 public CheckCLDR.Phase getCPhase() { 191 return cphase; 192 } 193 } 194 195 public enum ReportMenu { 196 PRIORITY_ITEMS("Dashboard", SurveyMain.R_VETTING_JSON), DATE_TIME("Date/Time", "r_datetime"), ZONES("Zones", "r_zones"), NUMBERS("Numbers", 197 "r_compact"); 198 199 private String display; 200 private String url; 201 ReportMenu(String d, String u)202 private ReportMenu(String d, String u) { 203 display = d; 204 url = u; 205 } 206 urlStub()207 public String urlStub() { 208 return url; 209 } 210 urlQuery()211 public String urlQuery() { 212 return SurveyMain.QUERY_SECTION + "=" + url; 213 } 214 215 /** 216 * 217 * @param base 218 * @param locale 219 * @return 220 * 221 * Called from menu_top.jsp only 222 */ urlFull(String base, String locale)223 public String urlFull(String base, String locale) { 224 return base + "?_=" + locale + "&" + urlQuery(); 225 } 226 display()227 public String display() { 228 return display; 229 } 230 } 231 232 // ===== Configuration state 233 private static Phase currentPhase = Phase.VETTING; 234 /** set by CLDR_PHASE property. **/ 235 private static String oldVersion = "OLDVERSION"; 236 private static String lastVoteVersion = "LASTVOTEVERSION"; 237 private static String newVersion = "NEWVERSION"; 238 239 public static boolean isConfigSetup = false; 240 241 /** 242 * @return the isUnofficial. - will return true (even in production) until configfile is setup 243 * @see CLDRConfig#getEnvironment() 244 */ isUnofficial()245 public static final boolean isUnofficial() { 246 if (!isConfigSetup) { 247 return true; // 248 } 249 return !(CLDRConfig.getInstance().getEnvironment() == CLDRConfig.Environment.PRODUCTION); 250 } 251 252 /** set to true for all but the official installation of ST. **/ 253 254 // ==== caches and general state 255 256 public UserRegistry reg = null; 257 public XPathTable xpt = null; 258 public SurveyForum fora = null; 259 static ElapsedTimer uptime = new ElapsedTimer("uptime: {0}"); 260 public static String isBusted = null; 261 private static String isBustedStack = null; 262 private static ElapsedTimer isBustedTimer = null; 263 private static ServletConfig config = null; 264 public static OperatingSystemMXBean osmxbean = ManagementFactory.getOperatingSystemMXBean(); 265 private static double nProcs = osmxbean.getAvailableProcessors(); 266 267 /** 268 * Is the CPU essentially busy? 269 * 270 * @return 271 */ hostBusy()272 public static final boolean hostBusy() { 273 return (osmxbean.getSystemLoadAverage() * 2) >= osmxbean.getAvailableProcessors(); 274 } 275 276 // ===== Special bug numbers. 277 private static final String URL_HOST = "http://www.unicode.org/"; 278 public static final String URL_CLDR = URL_HOST + "cldr/"; 279 280 /* 281 * TODO: CLDR no longer uses trac; change BUG_URL_BASE to link to github instead 282 */ 283 public static final String BUG_URL_BASE = URL_CLDR + "trac"; 284 285 public static final String GENERAL_HELP_URL = "https://sites.google.com/site/cldr/translation"; 286 public static final String GENERAL_HELP_NAME = "Instructions"; 287 288 public static final String ADMIN_HELP_URL = "http://cldr.unicode.org/index/survey-tool/admin"; 289 290 // ===== url prefix for help 291 public static final String CLDR_HELP_LINK = URL_CLDR + "survey_tool.html" + "#"; 292 293 // ===== Hash keys and field values 294 public static final String PROPOSED_DRAFT = "proposed-draft"; 295 296 /** 297 * 298 * @param ctx 299 * @return 300 * 301 * Called from st_top.jsp, and locally 302 */ modifyThing(WebContext ctx)303 public static String modifyThing(WebContext ctx) { 304 return " " + ctx.modifyThing("You are allowed to modify this locale."); 305 } 306 307 // ========= SYSTEM PROPERTIES 308 public static String vap = System.getProperty("CLDR_VAP"); // Vet Access Password 309 public static String testpw = System.getProperty("CLDR_TESTPW"); // Vet Access Password 310 private static String vetdata = System.getProperty("CLDR_VET_DATA"); // dir for vetted data 311 private File _vetdir = null; 312 313 /** 314 * @deprecated use CLDRURLS 315 */ 316 @Deprecated 317 private String defaultBase = CLDRURLS.DEFAULT_BASE + "/survey"; /* base URL */ 318 public static String fileBase = null; // not static - may change later. 319 // Common dir 320 public static String fileBaseSeed = null; // not static - may change later. 321 322 private static String specialMessage = System.getProperty("CLDR_MESSAGE"); // static 323 // - may 324 // change 325 // later 326 private static String lockOut = System.getProperty("CLDR_LOCKOUT"); // static 327 // - may 328 // change 329 // later 330 private static long specialTimer = 0; // 0 means off. Nonzero: expiry time of 331 // countdown. 332 333 // ======= query fields 334 public static final String QUERY_PASSWORD = "pw"; 335 public static final String QUERY_PASSWORD_ALT = "uid"; 336 public static final String QUERY_EMAIL = "email"; 337 public static final String QUERY_SESSION = "s"; 338 public static final String QUERY_LOCALE = "_"; 339 public static final String QUERY_SECTION = "x"; 340 private static final String QUERY_EXAMPLE = "e"; 341 public static final String QUERY_DO = "do"; 342 343 static final String SURVEYTOOL_COOKIE_SESSION = CookieSession.class.getPackage().getName() + ".id"; 344 static final String SURVEYTOOL_COOKIE_NONE = "0"; 345 static final String PREF_SORTMODE = "p_sort"; 346 private static final String PREF_SHOWLOCKED = "p_showlocked"; 347 static final String PREF_NOPOPUPS = "p_nopopups"; 348 static final String PREF_CODES_PER_PAGE = "p_pager"; 349 static final String PREF_SORTMODE_CODE = "code"; 350 static final String PREF_SORTMODE_CODE_CALENDAR = "codecal"; 351 static final String PREF_SORTMODE_METAZONE = "metazon"; 352 static final String PREF_SORTMODE_WARNING = "interest"; 353 static final String PREF_SORTMODE_NAME = "name"; 354 static final String PREF_SORTMODE_DEFAULT = PREF_SORTMODE_CODE; 355 public static final String PREF_NOJAVASCRIPT = "p_nojavascript"; 356 public static final String PREF_DEBUGJSP = "p_debugjsp"; // debug JSPs? 357 public static final String PREF_COVLEV = "p_covlev"; // covlev 358 private static final String PREF_COVTYP = "p_covtyp"; // covtyp 359 360 static final String TRANS_HINT_ID = "en_ZZ"; // Needs to be en_ZZ as per cldrbug #2918 361 public static final ULocale TRANS_HINT_LOCALE = new ULocale(TRANS_HINT_ID); 362 public static final String TRANS_HINT_LANGUAGE_NAME = TRANS_HINT_LOCALE.getDisplayLanguage(TRANS_HINT_LOCALE); // Note: 363 // Only 364 // shows 365 // language. 366 367 // ========== lengths 368 /** 369 * @see WebContext#prefCodesPerPage() 370 */ 371 static final int CODES_PER_PAGE = 1024; // This is only a default. 372 373 public static String xMAIN = "general"; 374 375 public static final String SHOWHIDE_SCRIPT = "<script><!-- \n" 376 + "function show(what)\n" 377 + "{document.getElementById(what).style.display=\"block\";\ndocument.getElementById(\"h_\"+what).style.display=\"none\";}\n" 378 + "function hide(what)\n" 379 + "{document.getElementById(what).style.display=\"none\";\ndocument.getElementById(\"h_\"+what).style.display=\"block\";}\n" 380 + "--></script>"; 381 382 private static HelpMessages surveyToolSystemMessages = null; 383 private static String CLDR_SURVEYTOOL_HASH = null; 384 sysmsg(String msg)385 private static String sysmsg(String msg) { 386 try { 387 if (surveyToolSystemMessages == null) { 388 surveyToolSystemMessages = new HelpMessages("st_sysmsg.html"); 389 } 390 return surveyToolSystemMessages.find(msg); 391 } catch (Throwable t) { 392 SurveyLog.logger.warning("Err " + t.toString() + " while trying to load sysmsg " + msg); 393 return "[MISSING MSG: " + msg + "]"; 394 } 395 } 396 397 /** 398 * Initialize servlet 399 * 400 * @param req 401 * @return the SurveyMain instance 402 */ getInstance(HttpServletRequest req)403 public static SurveyMain getInstance(HttpServletRequest req) { 404 if (config == null) { 405 return null; // not initialized. 406 } 407 return (SurveyMain) config.getServletContext().getAttribute(SurveyMain.class.getName()); 408 } 409 setInstance(HttpServletRequest req)410 private void setInstance(HttpServletRequest req) { 411 config.getServletContext().setAttribute(SurveyMain.class.getName(), this); 412 } 413 414 /** 415 * This function overrides GenericServlet.init. 416 * Called by StandardWrapper.initServlet automatically. 417 * Never called for cldr-apps TestAll.java. 418 */ 419 @Override init(final ServletConfig config)420 public final void init(final ServletConfig config) throws ServletException { 421 System.out.println("\n\n\n------------------- SurveyMain.init() ------------ " + uptime); 422 try { 423 new com.ibm.icu.text.SimpleDateFormat(); // Ensure that ICU is 424 // available before we get 425 // any farther 426 super.init(config); 427 CLDRConfigImpl.setCldrHome(config.getInitParameter("cldr.home")); 428 SurveyMain.config = config; 429 430 // verify config sanity 431 CLDRConfig cconfig = CLDRConfigImpl.getInstance(); 432 try(InputStream is = config.getServletContext().getResourceAsStream(JarFile.MANIFEST_NAME)) { 433 Manifest mf = new Manifest(is); 434 String s = mf.getMainAttributes().getValue("CLDR-Apps"+"-Git-Commit"); 435 if(s != null && !s.isEmpty()) { 436 SurveyMain.CLDR_SURVEYTOOL_HASH = s; 437 ((CLDRConfigImpl)cconfig).setCldrAppsHash(s); 438 System.err.println("Updated CLDR_APPS_HASH to " + s); 439 } else { 440 System.err.println("CLDR_APPS_HASH = unknown (no value in manifest)"); 441 } 442 } catch(Throwable t) { 443 System.err.println("CLDR_APPS_HASH = unknown - " + t.toString()); 444 } 445 isConfigSetup = true; // we have a CLDRConfig - so config is setup. 446 447 stopIfMaintenance(); 448 449 cconfig.getSupplementalDataInfo(); // will fail if CLDR_DIR is broken. 450 451 PathHeader.PageId.forString(PathHeader.PageId.Africa.name()); // Make 452 // sure 453 // cldr-tools 454 // is 455 // functioning. 456 startupThread.addTask(new SurveyThread.SurveyTask("startup") { 457 @Override 458 public void run() throws Throwable { 459 doStartup(); 460 } 461 }); 462 } catch (Throwable t) { 463 SurveyLog.logException(t, "Initializing SurveyTool"); 464 SurveyMain.busted("Error initializing SurveyTool.", t); 465 return; 466 } 467 468 try { 469 dbUtils = DBUtils.getInstance(); 470 } catch (Throwable t) { 471 SurveyLog.logException(t, "Starting up database"); 472 473 String dbBroken = DBUtils.getDbBrokenMessage(); 474 475 SurveyMain.busted("Error starting up database - " + dbBroken, t); 476 return; 477 } 478 479 try { 480 startupThread.start(); 481 SurveyLog.logger.warning("Startup thread launched"); 482 } catch (Throwable t) { 483 SurveyLog.logException(t, "Starting up startupThread"); 484 SurveyMain.busted("Error starting up startupThread", t); 485 return; 486 } 487 } 488 SurveyMain()489 public SurveyMain() { 490 super(); 491 CookieSession.sm = this; 492 } 493 494 /** 495 * output MIME header, build context, and run code.. 496 */ 497 @Override doPost(HttpServletRequest request, HttpServletResponse response)498 public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 499 doGet(request, response); 500 } 501 502 public static String defaultServletPath = null; 503 /** 504 * IP exclusion list 505 */ 506 static Hashtable<String, Object> BAD_IPS = new Hashtable<>(); 507 public static String fileBaseA; 508 public static String fileBaseASeed; 509 510 @Override doGet(HttpServletRequest request, HttpServletResponse response)511 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 512 if (respondToBogusRequest(request, response)) { 513 return; 514 } 515 CLDRConfigImpl.setUrls(request); 516 517 if (!ensureStartup(request, response)) { 518 return; 519 } 520 521 if (!isBusted()) { 522 response.setHeader("Cache-Control", "no-cache"); 523 response.setDateHeader("Expires", 0); 524 response.setHeader("Pragma", "no-cache"); 525 response.setDateHeader("Max-Age", 0); 526 response.setHeader("Robots", "noindex,nofollow"); 527 528 // handle raw xml 529 try { 530 if (getOutputFileManager().doRawXml(request, response)) { 531 // not counted. 532 xpages++; 533 return; 534 } 535 } catch (Throwable t) { 536 SurveyLog.logException(t, "raw XML"); 537 SurveyLog.logger.warning("Error on doRawXML: " + t.toString()); 538 t.printStackTrace(); 539 response.setContentType("text/plain"); 540 ServletOutputStream os = response.getOutputStream(); 541 os.println("Error processing raw XML:\n\n"); 542 t.printStackTrace(new PrintStream(os)); 543 xpages++; 544 return; 545 } 546 pages++; 547 548 if ((pages % 100) == 0) { 549 freeMem(pages, xpages); 550 } 551 } 552 com.ibm.icu.dev.util.ElapsedTimer reqTimer = new com.ibm.icu.dev.util.ElapsedTimer(); 553 554 /** 555 * Busted: unrecoverable error, do not attempt to go on. 556 */ 557 if (isBusted()) { 558 String pi = request.getParameter("sql"); // allow sql 559 if ((pi == null) || (!pi.equals(vap))) { 560 response.setContentType("text/html; charset=utf-8"); 561 PrintWriter out = response.getWriter(); 562 out.println("<html>"); 563 out.println("<head>"); 564 out.println("<title>CLDR Survey Tool offline</title>"); 565 out.println("<link rel='stylesheet' type='text/css' href='" + request.getContextPath() + "/" + "surveytool.css" 566 + "'>"); 567 showOfflinePage(request, response, out); 568 return; 569 } 570 } 571 572 /** 573 * User database request 574 * 575 */ 576 if (request.getParameter("udump") != null && request.getParameter("udump").equals(vap)) { // XML. 577 response.setContentType("application/xml; charset=utf-8"); 578 WebContext xctx = new WebContext(request, response); 579 doUDump(xctx); 580 xctx.close(); 581 return; 582 } 583 584 // rest of these are HTML 585 response.setContentType("text/html; charset=utf-8"); 586 587 // set up users context object 588 589 WebContext ctx = new WebContext(request, response); 590 ctx.reqTimer = reqTimer; 591 ctx.sm = this; 592 if (defaultServletPath == null) { 593 defaultServletPath = ctx.request.getServletPath(); 594 } 595 596 String baseThreadName = Thread.currentThread().getName(); 597 598 try { 599 600 // process any global redirects here. 601 602 if (isUnofficial()) { 603 boolean waitASec = twidBool("SurveyMain.twoSecondPageDelay"); 604 if (waitASec) { 605 ctx.println("<h1>twoSecondPageDelay</h1>"); 606 Thread.sleep(2000); 607 } 608 } 609 610 if (isUnofficial() && (ctx.hasTestPassword() || ctx.hasAdminPassword()) 611 && ctx.field("action").equals("new_and_login")) { // accessed from createAndLogin.jsp 612 ctx.println("<hr>"); 613 String real = ctx.field("real").trim(); 614 if (real.isEmpty() || real.equals("REALNAME")) { 615 ctx.println(ctx.iconHtml("stop", "fail") 616 + "<b>Please go <a href='javascript:window.history.back();'>Back</a> and fill in your real name.</b>"); 617 } else { 618 final boolean autoProceed = ctx.hasField("new_and_login_autoProceed"); 619 final boolean stayLoggedIn = ctx.hasField("new_and_login_stayLoggedIn"); 620 ctx.println("<div style='margin: 2em;'>"); 621 if (autoProceed) { 622 ctx.println("<img src='loader.gif' align='right'>"); 623 } else { 624 ctx.println("<img src='STLogo.png' align='right'>"); 625 } 626 UserRegistry.User u = reg.getEmptyUser(); 627 StringBuffer myRealName = new StringBuffer(real.trim()); 628 StringBuilder newRealName = new StringBuilder(); 629 for (int j = 0; j < myRealName.length(); j++) { 630 if (supportedNameSet.contains(myRealName.charAt(j))) { 631 newRealName.append(myRealName.charAt(j)); 632 } 633 } 634 u.org = ctx.field("new_org").trim(); 635 String randomEmail = UserRegistry.makePassword(null) + "@" + UserRegistry.makePassword(null).substring(0, 4).replace('.', '0') 636 + "." + u.org.replaceAll("_", "-") + ".example.com"; 637 String randomPass = UserRegistry.makePassword(null); 638 u.name = newRealName.toString() + "_TESTER_"; 639 u.email = newRealName + "." + randomEmail.trim(); 640 String newLocales = ctx.field("new_locales").trim(); 641 newLocales = UserRegistry.normalizeLocaleList(newLocales); 642 if (newLocales.isEmpty()) newLocales = "und"; 643 u.locales = newLocales; 644 u.password = randomPass; 645 u.userlevel = ctx.fieldInt("new_userlevel", -1); 646 if (u.userlevel <= 0) { 647 u.userlevel = 999; // nice try 648 } 649 UserRegistry.User registeredUser = reg.newUser(ctx, u); 650 ctx.println("<i>" + ctx.iconHtml("okay", "added") + "'" + u.name 651 + "'. <br>Email: " + u.email + " <br>Password: " + u.password + " <br>userlevel: " + u.getLevel() + "<br>"); 652 if (autoProceed) { 653 ctx.print("You should be logged in shortly, otherwise click this link:"); 654 } else { 655 ctx.print("You will be logged in when you click this link:"); 656 } 657 ctx.print("</i>"); 658 ctx.println("<br>"); 659 registeredUser.printPasswordLink(ctx); 660 ctx.println("<br><br><br><br><i>Note: this is a test account, and may be removed at any time.</i>"); 661 if (stayLoggedIn) { 662 ctx.addCookie(QUERY_EMAIL, u.email, TWELVE_WEEKS); 663 ctx.addCookie(QUERY_PASSWORD, u.password, TWELVE_WEEKS); 664 } else { 665 WebContext.removeLoginCookies(request, response); 666 } 667 if (autoProceed) { 668 ctx.println("<script>window.setTimeout(function(){document.location = '" + ctx.base() + "/v?email=" + u.email + "&pw=" + u.password 669 + "';},3000);</script>"); 670 } 671 ctx.println("</div>"); 672 } 673 } else if (ctx.hasAdminPassword()) { 674 ctx.response.sendRedirect(ctx.context("AdminPanel.jsp") + "?vap=" + vap); 675 return; 676 } else if (ctx.field("sql").equals(vap)) { 677 Thread.currentThread().setName(baseThreadName + " ST sql"); 678 doSql(ctx); // SQL interface 679 } else { 680 Thread.currentThread().setName(baseThreadName + " ST "); 681 doSession(ctx); // Session-based Survey main 682 } 683 } catch (Throwable t) { // should be THrowable 684 t.printStackTrace(); 685 SurveyLog.logException(t, ctx); 686 ctx.println("<div class='ferrbox'><h2>Error processing session: </h2><pre>" + t.toString() + "</pre></div>"); 687 SurveyLog.logger.warning("Failure with user: " + t); 688 } finally { 689 Thread.currentThread().setName(baseThreadName); 690 ctx.close(); 691 } 692 } 693 694 /** 695 * Avoid wasting time on response, or clogging logs with exceptions, if request is bogus. 696 * Respond to bogus requests with SC_NOT_FOUND. 697 * 698 * "Bogus" (for now) means the request to SurveyMain includes obsolete "x=r_...". 699 * Note that the remaining non-bogus requests for "x=r_..." are all to SurveyAjax, not SurveyMain. 700 * 701 * st.unicode.org receives many 702 * requests with "x=r_steps" from web-crawling robots. Sample July 2019 from /var/log/nginx/access.log: 703 * "GET /cldr-apps/survey?_=ar_AE&s__=93A...&step=time_formats&x=r_steps HTTP/1.1" 704 * 200 5284 "-" "Mozilla/5.0 (compatible; SemrushBot/3~bl; +http://www.semrush.com/bot.html)" 705 * 706 * Since r_vetting.jsp was removed, we also get bogus requests for "r_vetting.jsp". 707 * 708 * Reference: https://unicode-org.atlassian.net/browse/CLDR-13135, https://unicode-org.atlassian.net/browse/CLDR-13764 709 * 710 * @param request the HttpServletRequest 711 * @param response the HttpServletResponse 712 * @return true if the request is bogus, else false 713 * 714 * @throws IOException 715 */ respondToBogusRequest(HttpServletRequest request, HttpServletResponse response)716 private boolean respondToBogusRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { 717 String x = request.getParameter("x"); 718 if (x != null && x.startsWith("r_")) { 719 response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404 720 return true; 721 } 722 return false; 723 } 724 725 /** 726 * @param request 727 * @param response 728 * @param out 729 * @throws ServletException 730 * @throws IOException 731 */ showOfflinePage(HttpServletRequest request, HttpServletResponse response, PrintWriter out)732 private void showOfflinePage(HttpServletRequest request, HttpServletResponse response, PrintWriter out) throws ServletException, IOException { 733 out.println(SHOWHIDE_SCRIPT); 734 SurveyAjax.includeAjaxScript(request, response, SurveyAjax.AjaxType.STATUS); 735 // don't flood server if busted- check every minute. 736 out.println("<script>timerSpeed = 60080;</script>"); 737 out.print("<div id='st_err'><!-- for ajax errs --></div><span id='progress'>"); 738 // This next is for the DB Busted page, so we can show the MySQL configurator. 739 out.print("<script src='"+ request.getContextPath()+request.getServletPath() + "/../js/cldr-setup.js" + "'></script>"); 740 out.print(getTopBox()); 741 out.println("</span>"); 742 out.println("<hr>"); 743 out.println("<p class='ferrbox'>An Administrator must intervene to bring the Survey Tool back online."); 744 if (isUnofficial() || !isConfigSetup) { 745 final File maintFile = getHelperFile(); 746 if (!maintFile.exists() && request != null) { 747 try { 748 writeHelperFile(request, maintFile); 749 } catch (IOException e) { 750 SurveyLog.warnOnce("Trying to write helper file " + maintFile.getAbsolutePath() + " - " + e.toString()); 751 } 752 } 753 if (maintFile.exists()) { 754 out.println("<br/>If you are the administrator, try opening <a href='file://" + maintFile.getAbsolutePath() + "'>" 755 + maintFile.getAbsolutePath() + "</a> to choose setup mode."); 756 } else { 757 out.println("<br/>If you are the administrator, try loading the main SurveyTool page to create <a style='color: gray' href='file://" 758 + maintFile.getAbsolutePath() + "'>" + maintFile.getAbsolutePath() + "</a>"); 759 } 760 } else { 761 out.println("<br/> See: <a href='http://cldr.unicode.org/index/survey-tool#TOC-FAQ-Known-Bugs'>FAQ and Known Bugs</a>"); 762 } 763 out.println("</p> <br> " 764 + " <i>This message has been viewed " + pages + " time(s), SurveyTool has been down for " + isBustedTimer 765 + "</i>"); 766 } 767 768 /** 769 * Make sure we're started up, otherwise tell 'em, "please wait.." 770 * 771 * @param request 772 * @param response 773 * @return true if started, false if we are not (on false, get out, we're 774 * done printing..) 775 * @throws IOException 776 * @throws ServletException 777 */ ensureStartup(HttpServletRequest request, HttpServletResponse response)778 private boolean ensureStartup(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 779 setInstance(request); 780 if (!isSetup) { 781 782 stopIfMaintenance(request); 783 784 boolean isGET = "GET".equals(request.getMethod()); 785 int sec = 600; // was 4 786 if (isBusted != null) { 787 sec = 300; 788 } 789 String base = WebContext.base(request); 790 String loadOnOk = base; 791 if (isGET) { 792 String qs = ""; 793 String pi = ""; 794 if (request.getPathInfo() != null && request.getPathInfo().length() > 0) { 795 pi = request.getPathInfo(); 796 } 797 if (request.getQueryString() != null && request.getQueryString().length() > 0) { 798 qs = "?" + request.getQueryString(); 799 } 800 loadOnOk = base + pi + qs; 801 response.setHeader("Refresh", sec + "; " + loadOnOk); 802 } else { 803 loadOnOk = base + "?sorryPost=1"; 804 } 805 response.setContentType("text/html; charset=utf-8"); 806 PrintWriter out = response.getWriter(); 807 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head>"); 808 out.println("<title>" + sysmsg("startup_title") + "</title>"); 809 out.println("<link rel='stylesheet' type='text/css' href='" + base + "/../surveytool.css'>"); 810 SurveyAjax.includeAjaxScript(request, response, SurveyAjax.AjaxType.STATUS); 811 if (isUnofficial()) { 812 out.println("<script>timerSpeed = 2500;</script>"); 813 } else { 814 out.println("<script>timerSpeed = 10000;</script>"); 815 } 816 // todo: include st_top.jsp instead 817 out.println("</head><body>"); 818 if (isUnofficial()) { 819 out.print("<div class='topnotices'><p class='unofficial' title='Not an official SurveyTool' >"); 820 out.print("Unofficial"); 821 out.println("</p></div>"); 822 } 823 if (isMaintenance()) { 824 final File maintFile = getHelperFile(); 825 final String maintMessage = getMaintMessage(maintFile, request); 826 out.println("<h2>Setting up the SurveyTool</h2>"); 827 out.println("<div class='st_setup'>"); 828 out.println(maintMessage); // TODO 829 out.println("</div>"); 830 out.println("<hr>"); 831 } else if (isBusted != null) { 832 showOfflinePage(request, response, out); 833 } else { 834 out.print(sysmsg("startup_header")); 835 out.print("<div id='st_err'><!-- for ajax errs --></div><span id='progress'>" + getTopBox() + "</span>"); 836 out.print(sysmsg("startup_wait")); 837 } 838 out.println("<br><i id='uptime'> " + getGuestsAndUsers() + "</i><br>"); 839 // TODO: on up, goto <base> 840 841 out.println("<script>loadOnOk = '" + loadOnOk + "';</script>"); 842 out.println("<script>clickContinue = '" + loadOnOk + "';</script>"); 843 if (!isMaintenance()) { 844 if (!isGET) { 845 out.println("(Sorry, we can't automatically retry your " + request.getMethod() 846 + " request - you may attempt Reload in a few seconds " + "<a href='" + base + "'>or click here</a><br>"); 847 } else { 848 out.println("If this page does not load in " + sec + " seconds, you may <a href='" + base 849 + "'>click here to go to the main Survey Tool page</a>"); 850 } 851 } 852 out.println("<noscript><h1>JavaScript is required for logging into the SurveyTool.</h1></noscript>"); 853 out.print(sysmsg("startup_footer")); 854 out.println("<span id='visitors'></span>"); 855 out.print(getCurrev(true)); 856 out.print("</body></html>"); 857 return false; 858 } else { 859 return true; 860 } 861 } 862 863 /** 864 * @return the fileBase 865 */ getFileBase()866 private static String getFileBase() { 867 if (fileBase == null) { 868 CLDRConfig survprops = CLDRConfig.getInstance(); 869 File base = survprops.getCldrBaseDirectory(); 870 fileBase = new File(base, "common/main").getAbsolutePath(); 871 fileBaseSeed = new File(base, "seed/main").getAbsolutePath(); 872 File commonAnnotations = new File(base, "common/annotations"); 873 fileBaseA = commonAnnotations.getAbsolutePath(); 874 commonAnnotations.mkdirs(); // make sure this exists 875 File seedAnnotations = new File(base, "seed/annotations"); 876 seedAnnotations.mkdirs(); // make sure this exists 877 fileBaseASeed = seedAnnotations.getAbsolutePath(); 878 } 879 if (fileBase == null) 880 throw new NullPointerException("fileBase==NULL"); 881 return fileBase; 882 } 883 884 /** 885 * Get all of the file bases as an array 886 * @return 887 */ getFileBases()888 private static File[] getFileBases() { 889 getFileBase(); // load these 890 File files[] = { new File(getFileBase()), 891 new File(getFileBaseSeed()), 892 new File(fileBaseA), 893 new File(fileBaseASeed) 894 }; 895 return files; 896 } 897 898 /** 899 * @return 900 */ getSurveyHome()901 public static String getSurveyHome() { 902 String cldrHome; 903 CLDRConfig survprops = CLDRConfig.getInstance(); 904 905 if (!(survprops instanceof CLDRConfigImpl)) { 906 File tmpHome = new File("testing_cldr_home"); 907 if (!tmpHome.isDirectory()) { 908 if (!tmpHome.mkdir()) { 909 throw new InternalError("Couldn't create " + tmpHome.getAbsolutePath()); 910 } 911 } 912 cldrHome = tmpHome.getAbsolutePath(); 913 System.out.println("NOTE: not inside of web process, using temporary CLDRHOME " + cldrHome); 914 } else { 915 cldrHome = survprops.getProperty("CLDRHOME"); 916 } 917 if (cldrHome == null) 918 throw new NullPointerException("CLDRHOME==null"); 919 return cldrHome; 920 } 921 922 /** 923 * @return the fileBaseSeed 924 */ getFileBaseSeed()925 private static String getFileBaseSeed() { 926 if (fileBaseSeed == null) { 927 getFileBase(); 928 } 929 if (fileBaseSeed == null) 930 throw new NullPointerException("fileBaseSeed==NULL"); 931 return fileBaseSeed; 932 } 933 934 /** 935 * SQL Console 936 */ doSql(WebContext ctx)937 private void doSql(WebContext ctx) { 938 printHeader(ctx, "SQL Console@" + localhost()); 939 ctx.println("<script>timerSpeed = 6000;</script>"); 940 String q = ctx.field("q"); 941 boolean tblsel = false; 942 printAdminMenu(ctx); 943 ctx.println("<h1>SQL Console (" + DBUtils.getDBKind() + ")</h1>"); 944 945 ctx.println("<i style='font-size: small; color: silver;'>" + DBUtils.getInstance().getDBInfo() + "</i><br/>"); 946 947 if (isBusted != null) { // This may or may 948 // not work. Survey 949 // Tool is busted, 950 // can we attempt 951 // to get in via 952 // SQL? 953 ctx.println("<h4>ST not currently started, attempting to make SQL available</h4>"); 954 ctx.println("<pre>"); 955 specialMessage = "<b>SurveyTool is in an administrative mode- please log off.</b>"; 956 try { 957 doStartupDB(); 958 } catch (Throwable t) { 959 SurveyLog.logException(t, ctx); 960 ctx.println("Caught: " + t.toString() + "\n"); 961 } 962 ctx.println("</pre>"); 963 } 964 965 if (q.length() == 0) { 966 q = DBUtils.DB_SQL_ALLTABLES; 967 tblsel = true; 968 } else { 969 ctx.println("<a href='" + ctx.base() + "?sql=" + vap + "'>[List of Tables]</a>"); 970 } 971 ctx.println("<form method=POST action='" + ctx.base() + "'>"); 972 ctx.println("<input type=hidden name=sql value='" + vap + "'>"); 973 ctx.println("SQL: <input class='inputbox' name=q size=80 cols=80 value=\"" + q + "\"><br>"); 974 ctx.println("<label style='border: 1px'><input type=checkbox name=unltd>Show all?</label> "); 975 ctx.println("<label style='border: 1px'><input type=checkbox name=isUpdate>U/I/D?</label> "); 976 ctx.println("<input type=submit name=do value=Query>"); 977 ctx.println("</form>"); 978 979 if (q.length() > 0) { 980 SurveyLog.logger.severe("Raw SQL: " + q); 981 ctx.println("<hr>"); 982 ctx.println("query: <tt>" + q + "</tt><br><br>"); 983 Connection conn = null; 984 Statement s = null; 985 try { 986 int i, j; 987 988 com.ibm.icu.dev.util.ElapsedTimer et = new com.ibm.icu.dev.util.ElapsedTimer(); 989 990 conn = dbUtils.getDBConnection(); 991 s = conn.createStatement(); 992 if (ctx.field("isUpdate").length() > 0) { 993 int rc = s.executeUpdate(q); 994 conn.commit(); 995 ctx.println("<br>Result: " + rc + " row(s) affected.<br>"); 996 } else { 997 ResultSet rs = s.executeQuery(q); 998 conn.commit(); 999 1000 ResultSetMetaData rsm = rs.getMetaData(); 1001 int cc = rsm.getColumnCount(); 1002 1003 ctx.println("<table summary='SQL Results' class='sqlbox' border='2'><tr><th>#</th>"); 1004 for (i = 1; i <= cc; i++) { 1005 ctx.println("<th>" + rsm.getColumnName(i) + "<br>"); 1006 int t = rsm.getColumnType(i); 1007 switch (t) { 1008 case java.sql.Types.VARCHAR: 1009 ctx.println("VARCHAR"); 1010 break; 1011 case java.sql.Types.INTEGER: 1012 ctx.println("INTEGER"); 1013 break; 1014 case java.sql.Types.BLOB: 1015 ctx.println("BLOB"); 1016 break; 1017 case java.sql.Types.TIMESTAMP: 1018 ctx.println("TIMESTAMP"); 1019 break; 1020 case java.sql.Types.BINARY: 1021 ctx.println("BINARY"); 1022 break; 1023 case java.sql.Types.LONGVARBINARY: 1024 ctx.println("LONGVARBINARY"); 1025 break; 1026 default: 1027 ctx.println("type#" + t); 1028 break; 1029 } 1030 ctx.println("(" + rsm.getColumnDisplaySize(i) + ")"); 1031 ctx.println("</th>"); 1032 } 1033 if (tblsel) { 1034 ctx.println("<th>Info</th><th>Rows</th>"); 1035 } 1036 ctx.println("</tr>"); 1037 int limit = 30; 1038 if (ctx.field("unltd").length() > 0) { 1039 limit = 9999999; 1040 } 1041 for (j = 0; rs.next() && (j < limit); j++) { 1042 ctx.println("<tr class='r" + (j % 2) + "'><th>" + j + "</th>"); 1043 for (i = 1; i <= cc; i++) { 1044 String v; 1045 try { 1046 v = rs.getString(i); 1047 } catch (SQLException se) { 1048 if (se.getSQLState().equals("S1009")) { 1049 v = "0000-00-00 00:00:00"; 1050 } else { 1051 v = "(Err:" + DBUtils.unchainSqlException(se) + ")"; 1052 } 1053 } catch (Throwable t) { 1054 t.printStackTrace(); 1055 v = "(Err:" + t.toString() + ")"; 1056 } 1057 if (v != null) { 1058 ctx.println("<td>"); 1059 if (rsm.getColumnType(i) == java.sql.Types.LONGVARBINARY) { 1060 String uni = DBUtils.getStringUTF8(rs, i); 1061 ctx.println(uni + "<br>"); 1062 byte bytes[] = rs.getBytes(i); 1063 for (byte b : bytes) { 1064 ctx.println(Integer.toHexString((b) & 0xFF)); 1065 } 1066 } else { 1067 ctx.println(v); 1068 } 1069 ctx.print("</td>"); 1070 if (tblsel == true) { 1071 ctx.println("<td>"); 1072 ctx.println("<form method=POST action='" + ctx.base() + "'>"); 1073 ctx.println("<input type=hidden name=sql value='" + vap + "'>"); 1074 ctx.println("<input type=hidden name=q value='" + "select * from " + v + " where 1 = 0'>"); 1075 ctx.println("<input type=image src='" + ctx.context("zoom" + ".png") 1076 + "' value='Info'></form>"); 1077 ctx.println("</td><td>"); 1078 int count = DBUtils.sqlCount(ctx, conn, "select COUNT(*) from " + v); 1079 ctx.println(count + "</td>"); 1080 } 1081 } else { 1082 ctx.println("<td style='background-color: gray'></td>"); 1083 } 1084 } 1085 ctx.println("</tr>"); 1086 } 1087 1088 ctx.println("</table>"); 1089 rs.close(); 1090 } 1091 1092 ctx.println("elapsed time: " + et + "<br>"); 1093 } catch (SQLException se) { 1094 SurveyLog.logException(se, ctx); 1095 String complaint = "SQL err: " + DBUtils.unchainSqlException(se); 1096 1097 ctx.println("<pre class='ferrbox'>" + complaint + "</pre>"); 1098 SurveyLog.logger.severe(complaint); 1099 } catch (Throwable t) { 1100 SurveyLog.logException(t, ctx); 1101 String complaint = t.toString(); 1102 t.printStackTrace(); 1103 ctx.println("<pre class='ferrbox'>" + complaint + "</pre>"); 1104 SurveyLog.logger.severe("Err in SQL execute: " + complaint); 1105 } finally { 1106 try { 1107 s.close(); 1108 } catch (SQLException se) { 1109 SurveyLog.logException(se, ctx); 1110 String complaint = "in s.closing: SQL err: " + DBUtils.unchainSqlException(se); 1111 1112 ctx.println("<pre class='ferrbox'> " + complaint + "</pre>"); 1113 SurveyLog.logger.severe(complaint); 1114 } catch (Throwable t) { 1115 SurveyLog.logException(t, ctx); 1116 String complaint = t.toString(); 1117 ctx.println("<pre class='ferrbox'> " + complaint + "</pre>"); 1118 SurveyLog.logger.severe("Err in SQL close: " + complaint); 1119 } 1120 DBUtils.closeDBConnection(conn); 1121 } 1122 } 1123 printFooter(ctx); 1124 } 1125 1126 /** 1127 * @return memory statistics as a string 1128 */ freeMem()1129 public static String freeMem() { 1130 Runtime r = Runtime.getRuntime(); 1131 double total = r.totalMemory(); 1132 total = total / 1024000.0; 1133 double free = r.freeMemory(); 1134 free = free / 1024000.0; 1135 double used = total - free; 1136 return "Free memory: " + (int) free + "M / Used: " + (int) used + "M /: total: " + total + "M"; 1137 } 1138 freeMem(int pages, int xpages)1139 private static final void freeMem(int pages, int xpages) { 1140 SurveyLog.logger.warning("pages: " + pages + "+" + xpages + ", " + freeMem() + ".<br/>"); 1141 } 1142 1143 /** 1144 * Hash of twiddlable (toggleable) parameters 1145 * 1146 */ 1147 Hashtable<String, Boolean> twidHash = new Hashtable<>(); 1148 twidGetBool(String key, boolean defVal)1149 private boolean twidGetBool(String key, boolean defVal) { 1150 Boolean b = twidHash.get(key); 1151 if (b == null) { 1152 return defVal; 1153 } else { 1154 return b.booleanValue(); 1155 } 1156 } 1157 twidPut(String key, boolean val)1158 public void twidPut(String key, boolean val) { 1159 twidHash.put(key, new Boolean(val)); 1160 } 1161 1162 /* twiddle: these are params settable at runtime. 1163 * TODO: clarify, can the params change during a run of Survey Tool? How and when does that happen? */ twidBool(String x)1164 private boolean twidBool(String x) { 1165 return twidBool(x, false); 1166 } 1167 twidBool(String x, boolean defVal)1168 private synchronized boolean twidBool(String x, boolean defVal) { 1169 boolean ret = twidGetBool(x, defVal); 1170 twidPut(x, ret); 1171 return ret; 1172 } 1173 1174 /** 1175 * Admin panel 1176 * 1177 * @param ctx 1178 */ printAdminMenu(WebContext ctx)1179 private void printAdminMenu(WebContext ctx) { 1180 1181 boolean isDump = ctx.hasField("dump"); 1182 boolean isSql = ctx.hasField("sql"); 1183 1184 ctx.print("<div style='float: right'><a class='notselected' href='" + ctx.base() + "'><b>[SurveyTool main]</b></a> | "); 1185 ctx.print("<a class='notselected' href='" + ctx.base() + "?letmein=" + vap 1186 + "&email=admin@'><b>Login as admin@</b></a> | "); 1187 ctx.print("<a class='" + (isDump ? "" : "not") + "selected' href='" + ctx.context("AdminPanel.jsp") + "?vap=" + vap 1188 + "'>Admin</a>"); 1189 ctx.print(" | "); 1190 ctx.print("<a class='" + (isSql ? "" : "not") + "selected' href='" + ctx.base() + "?sql=" + vap + "'>SQL</a>"); 1191 ctx.print("<br>"); 1192 ctx.print("<a href=\"" + SurveyMain.ADMIN_HELP_URL + "\">Admin Help</a>"); 1193 ctx.println("</div>"); 1194 } 1195 1196 /* 1197 * print menu of stuff to 'work with' a live user session.. 1198 */ printLiveUserMenu(WebContext ctx, CookieSession cs)1199 private void printLiveUserMenu(WebContext ctx, CookieSession cs) { 1200 ctx.println("<a href='" + ctx.base() + "?dump=" + vap + "&see=" + cs.id + "'>" 1201 + ctx.iconHtml("zoom", "SEE this user") + "see" + "</a> |"); 1202 ctx.println("<a href='" + ctx.base() + "?&s=" + cs.id + "'>" + "be" + "</a> |"); 1203 ctx.println("<a href='" + ctx.base() + "?dump=" + vap + "&unlink=" + cs.id + "'>" + "kick" + "</a>"); 1204 } 1205 1206 /** 1207 * print the header of the thing 1208 */ printHeader(WebContext ctx, String title)1209 public void printHeader(WebContext ctx, String title) { 1210 ctx.includeFragment("st_header.jsp"); 1211 title = UCharacter.toTitleCase(SurveyMain.TRANS_HINT_LOCALE.toLocale(), title, null); 1212 1213 ctx.println("<META NAME=\"ROBOTS\" CONTENT=\"NOINDEX,NOFOLLOW\"> "); // NO 1214 // index 1215 ctx.println("<meta name='robots' content='noindex,nofollow'>"); 1216 ctx.println("<meta name=\"gigabot\" content=\"noindex\">"); 1217 ctx.println("<meta name=\"gigabot\" content=\"noarchive\">"); 1218 ctx.println("<meta name=\"gigabot\" content=\"nofollow\">"); 1219 ctx.println("<link rel='stylesheet' type='text/css' href='" + ctx.context("surveytool.css") + "'>"); 1220 ctx.includeAjaxScript(AjaxType.STATUS); 1221 ctx.println("<title>CLDR " + getNewVersion() + " Survey Tool: "); 1222 if (ctx.getLocale() != null) { 1223 ctx.print(ctx.getLocale().getDisplayName() + " | "); 1224 } 1225 ctx.println(title + "</title>"); 1226 ctx.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"); 1227 ctx.put("TITLE", title); 1228 ctx.includeFragment("st_top.jsp"); 1229 ctx.no_js_warning(); 1230 } 1231 getSpecialHeader()1232 private String getSpecialHeader() { 1233 return getSpecialHeader(null); 1234 } 1235 getSpecialHeader(WebContext ctx)1236 private String getSpecialHeader(WebContext ctx) { 1237 StringBuffer out = new StringBuffer(); 1238 String specialHeader = getSpecialHeaderText(); 1239 if ((specialHeader != null) && (specialHeader.length() > 0)) { 1240 out.append("<div class='specialHeader'>"); 1241 out.append(specialHeader); 1242 if (specialTimer != 0) { 1243 long t0 = System.currentTimeMillis(); 1244 out.append("<br><b>Timer:</b> "); 1245 if (t0 > specialTimer) { 1246 out.append("<b>The countdown time has arrived.</b>"); 1247 } else { 1248 out.append("The countdown timer has " + timeDiff(t0, specialTimer) + " remaining on it."); 1249 } 1250 } 1251 out.append("<br>"); 1252 String threadInfo = startupThread.htmlStatus(); 1253 if (threadInfo != null) { 1254 out.append("<b>Processing:" + threadInfo + "</b><br>"); 1255 } 1256 out.append(getProgress()); 1257 out.append("</div><br>"); 1258 } else { 1259 String threadInfo = startupThread.htmlStatus(); 1260 if (threadInfo != null) { 1261 out.append("<b>Processing:" + threadInfo + "</b><br>"); 1262 } 1263 out.append(getProgress()); 1264 } 1265 return out.toString(); 1266 } 1267 1268 /** 1269 * 1270 * @return 1271 * 1272 * Called by getSpecialHeader, and also called from v.jsp (but Eclipse won't show that in "Open call hierarchy" because it's jsp) 1273 */ getSpecialHeaderText()1274 public String getSpecialHeaderText() { 1275 String specialHeader = CLDRConfig.getInstance().getProperty("CLDR_HEADER"); 1276 if(specialHeader==null) return ""; 1277 return specialHeader; 1278 } 1279 statusJSON()1280 public JSONObject statusJSON() throws JSONException { 1281 Runtime r = Runtime.getRuntime(); 1282 double total = r.totalMemory(); 1283 total = total / 1024000.0; 1284 double free = r.freeMemory(); 1285 free = free / 1024000.0; 1286 1287 double load = osmxbean.getSystemLoadAverage(); 1288 CLDRConfig config = CLDRConfig.getInstance(); 1289 return new JSONObject().put("isBusted", isBusted).put("lockOut", lockOut != null).put("isSetup", isSetup) 1290 .put("isUnofficial", isUnofficial()).put("environment", config.getEnvironment().name()) 1291 .put("specialHeader", config.getProperty("CLDR_HEADER")) 1292 .put("specialTimerRemaining", specialTimer != 0 ? timeDiff(System.currentTimeMillis(), specialTimer) : null) 1293 .put("processing", startupThread.htmlStatus()).put("guests", CookieSession.getGuestCount()) 1294 .put("users", CookieSession.getUserCount()).put("uptime", uptime).put("surveyRunningStamp", surveyRunningStamp.current()) 1295 .put("memfree", free).put("memtotal", total).put("pages", pages).put("uptime", uptime).put("phase", phase()) 1296 .put("currev", SurveyMain.getCurrevCldrApps()) // Code only! 1297 .put("newVersion", newVersion).put("sysload", load).put("sysprocs", nProcs).put("dbopen", DBUtils.db_number_open) 1298 .put("dbused", DBUtils.db_number_used); 1299 } 1300 1301 /** 1302 * Return the entire top 'box' including progress bars, busted notices, etc. 1303 * 1304 * @return 1305 */ getTopBox()1306 private String getTopBox() { 1307 StringBuffer out = new StringBuffer(); 1308 if (isBusted != null) { 1309 out.append("<h1>The CLDR Survey Tool is offline</h1>"); 1310 out.append("<div class='ferrbox'><pre>" + isBusted + "</pre><hr>"); 1311 String stack = SurveyForum.HTMLSafe(isBustedStack).replaceAll("\t", " ").replaceAll("\n", "<br>"); 1312 out.append(getShortened(stack)); 1313 out.append("</div><br>"); 1314 } 1315 if (lockOut != null) { 1316 out.append("<h1>The CLDR Survey Tool is Locked for Maintenance</h1>"); 1317 } 1318 out.append(getSpecialHeader()); 1319 return out.toString(); 1320 } 1321 1322 /** 1323 * Progress bar width 1324 */ 1325 public static final int PROGRESS_WID = 100; 1326 1327 /* 1328 * (non-Javadoc) 1329 * 1330 * @see 1331 * org.unicode.cldr.web.CLDRProgressIndicator#openProgress(java.lang.String) 1332 */ 1333 @Override openProgress(String what)1334 public CLDRProgressTask openProgress(String what) { 1335 return openProgress(what, -100); 1336 } 1337 1338 /* 1339 * (non-Javadoc) 1340 * 1341 * @see 1342 * org.unicode.cldr.web.CLDRProgressIndicator#openProgress(java.lang.String, 1343 * int) 1344 */ 1345 @Override openProgress(String what, int max)1346 public CLDRProgressTask openProgress(String what, int max) { 1347 return progressManager.openProgress(what, max); 1348 } 1349 1350 /** 1351 * Return the current progress indicator. 1352 * 1353 * @return 1354 */ getProgress()1355 public String getProgress() { 1356 return progressManager.getProgress(); 1357 } 1358 1359 /** 1360 * Get the current source revision, as HTML 1361 * @return 1362 * 1363 * Called from jsp files as well as locally 1364 */ getCurrev()1365 public static String getCurrev() { 1366 return getCurrev(true); 1367 } 1368 1369 static final Pattern HASH_PATTERN = Pattern.compile("^CLDR_([A-Z]+)_HASH$"); 1370 /** 1371 * Get the current source revision 1372 * @param asLink true if HTML, false if plaintext 1373 * @return 1374 * 1375 * Called from jsp files as well as locally 1376 */ getCurrev(boolean asLink)1377 public static String getCurrev(boolean asLink) { 1378 JSONObject currev = getCurrevJSON(); 1379 StringBuilder output = new StringBuilder(); 1380 String lastLink = null; 1381 final Set<String> resultSet = new HashSet<>(); // If only one result ,return a single string. 1382 1383 for(Iterator<String> i = currev.keys(); i.hasNext();) { 1384 final String k = i.next().toString(); 1385 String v; 1386 output.append(' '); 1387 if(asLink) { 1388 final String friendly = HASH_PATTERN.matcher(k).replaceFirst("$1").toLowerCase(); 1389 output.append("<span title='"+k+"'>"+friendly+"</span>"); // use more friendly name 1390 } else { 1391 output.append(k); 1392 } 1393 output.append('='); 1394 try { 1395 v = currev.getString(k); 1396 if(asLink) { 1397 String link = CLDRURLS.gitHashToLink(v); 1398 output.append(link); 1399 resultSet.add(link); 1400 } else { 1401 output.append(v); 1402 resultSet.add(v); 1403 } 1404 } catch (JSONException e) { 1405 String message = "(exception: " + e.getMessage()+")"; 1406 output.append(message); 1407 resultSet.add(message); 1408 } 1409 } 1410 // If it is unanimous, return a single string. 1411 if(resultSet.size() == 1) { 1412 return resultSet.toArray()[0].toString(); // Return the single result. 1413 } 1414 return output.toString(); 1415 } 1416 1417 1418 /** 1419 * Get the git hash for cldr-apps, statically. 1420 * Use this to avoid dependency on a loaded CLDRConfig. 1421 * @return 1422 * @deprecated use getCurrevJSON 1423 */ 1424 @Deprecated getCurrevCldrApps()1425 public static String getCurrevCldrApps() { 1426 if (CLDR_SURVEYTOOL_HASH == null) { 1427 CLDR_SURVEYTOOL_HASH = CLDRConfigImpl.getGitHashForSlug("CLDR_APPS_HASH"); 1428 } 1429 return CLDR_SURVEYTOOL_HASH; 1430 } 1431 1432 /** 1433 * Get the current source revision, as a JSON object 1434 * This will either be a single string '(unknown)' or '1234568' 1435 * or, it will include error conditions: '12345678 CLDR_TOOLS_HASH=00bad000' 1436 * if one component is out of sync. 1437 * @return 1438 * 1439 * Called from ajax_status.jsp 1440 */ getCurrevJSON()1441 public static JSONObject getCurrevJSON() { 1442 final CLDRConfigImpl instance = CLDRConfigImpl.getInstance(); 1443 JSONObject jo = new JSONObject(); 1444 for(final String p : CLDRConfigImpl.ALL_GIT_HASHES) { 1445 try { 1446 jo.put(p, instance.getProperty(p, CLDRURLS.UNKNOWN_REVISION)); 1447 } catch (JSONException e) { 1448 // // TODO Auto-generated catch block 1449 // e.printStackTrace(); 1450 } 1451 } 1452 return jo; 1453 } 1454 1455 /** 1456 * 1457 * @param ctx 1458 * 1459 * Called from DisptePageManager.java and generalinfo.jsp 1460 */ printFooter(WebContext ctx)1461 public void printFooter(WebContext ctx) { 1462 ctx.includeFragment("st_footer.jsp"); 1463 } 1464 1465 /** 1466 * 1467 * @return 1468 * 1469 * Called from jsp files as well as locally 1470 */ getGuestsAndUsers()1471 public static String getGuestsAndUsers() { 1472 StringBuffer out = new StringBuffer(); 1473 int guests = CookieSession.getGuestCount(); 1474 int users = CookieSession.getUserCount(); 1475 if ((guests + users) > 0) { // ?? 1476 out.append("~"); 1477 if (users > 0) { 1478 out.append(users + " users"); 1479 } 1480 if (guests > 0) { 1481 if (users > 0) { 1482 out.append(", "); 1483 } 1484 out.append(" " + guests + " guests"); 1485 } 1486 } 1487 out.append(", " + pages + "pg/" + uptime); 1488 double procs = osmxbean.getAvailableProcessors(); 1489 double load = osmxbean.getSystemLoadAverage(); 1490 if (load > 0.0) { 1491 int n = 256 - (int) Math.floor((load / procs) * 256.0); 1492 String asTwoHexString = Integer.toHexString(n); 1493 out.append("/<span title='Total System Load' style='background-color: #ff"); 1494 if (asTwoHexString.length() == 1) { 1495 out.append("0"); 1496 out.append(asTwoHexString); 1497 out.append("0"); 1498 out.append(asTwoHexString); 1499 } else { 1500 out.append(asTwoHexString); 1501 out.append(asTwoHexString); 1502 } 1503 out.append("'>load:" + (int) Math.floor(load * 100.0) + "%</span>"); 1504 } 1505 { 1506 DBUtils theDb = DBUtils.peekInstance(); 1507 if (theDb != null) { 1508 try { 1509 out.append(" <span title='DB Connections/Max Connections'>db:"); 1510 theDb.statsShort(out); 1511 out.append("</span>"); 1512 } catch (IOException e) { 1513 // e.printStackTrace(); 1514 } 1515 } 1516 } 1517 return out.toString(); 1518 } 1519 1520 /** 1521 * process the '_' parameter, if present, and set the locale. 1522 */ setLocale(WebContext ctx)1523 private void setLocale(WebContext ctx) { 1524 String locale = ctx.field(QUERY_LOCALE); 1525 if (locale != null) { // knock out some bad cases 1526 if ((locale.indexOf('.') != -1) || (locale.indexOf('/') != -1)) { 1527 locale = null; 1528 } 1529 } 1530 // knock out nonexistent cases. 1531 if (locale != null && (locale.length() > 0)) { 1532 CLDRLocale l = CLDRLocale.getInstance(locale); 1533 if (getLocalesSet().contains(l)) { 1534 CLDRLocale theDefaultContent = getSupplementalDataInfo().getBaseFromDefaultContent(l); 1535 if (theDefaultContent != null) { 1536 l = theDefaultContent; 1537 } 1538 ctx.setLocale(l); 1539 } 1540 } 1541 } 1542 1543 /* print a user table without any extra help in it */ printUserTable(WebContext ctx)1544 private void printUserTable(WebContext ctx) { 1545 printUserTableWithHelp(ctx, null, null); 1546 } 1547 1548 /** 1549 * 1550 * @param ctx 1551 * @param helpLink 1552 * 1553 * Called by DisputePageManager as well as locally 1554 */ printUserTableWithHelp(WebContext ctx, String helpLink)1555 public void printUserTableWithHelp(WebContext ctx, String helpLink) { 1556 printUserTableWithHelp(ctx, helpLink, null); 1557 } 1558 1559 /** 1560 * Display information about one more users 1561 * 1562 * @param ctx 1563 * @param helpLink 1564 * @param helpName 1565 * 1566 * Called, for example, when the user chooses "Settings" under "My Account" in the gear menu 1567 */ printUserTableWithHelp(WebContext ctx, String helpLink, String helpName)1568 private void printUserTableWithHelp(WebContext ctx, String helpLink, String helpName) { 1569 ctx.put("helpLink", helpLink); 1570 ctx.put("helpName", helpName); 1571 ctx.includeFragment("usermenu.jsp"); 1572 } 1573 1574 /** 1575 * Accessed from usermenu.jsp 1576 */ 1577 public static final String REDO_FIELD_LIST[] = { QUERY_LOCALE, QUERY_SECTION, QUERY_DO, "forum" }; 1578 1579 /** 1580 * Handle creating a new user 1581 */ doNew(WebContext ctx)1582 private void doNew(WebContext ctx) { 1583 printHeader(ctx, "New User"); 1584 printUserTableWithHelp(ctx, "/AddModifyUser"); 1585 if (UserRegistry.userCanCreateUsers(ctx.session.user)) { 1586 showAddUser(ctx); 1587 } 1588 ctx.println("<a href='" + ctx.url() + "'><b>Main SurveyTool Page</b></a><hr>"); 1589 1590 String new_name = ctx.field("new_name"); 1591 String new_email = ctx.field("new_email"); 1592 String new_locales = ctx.field("new_locales"); 1593 new_locales = UserRegistry.normalizeLocaleList(new_locales); 1594 if (new_locales.isEmpty()) new_locales = "und"; 1595 String new_org = ctx.field("new_org"); 1596 int new_userlevel = ctx.fieldInt("new_userlevel", -1); 1597 1598 if (!UserRegistry.userCreateOtherOrgs(ctx.session.user)) { 1599 new_org = ctx.session.user.org; // if not admin, must create user in 1600 // the same org 1601 } 1602 1603 boolean newOrgOk = false; 1604 try { 1605 Organization.fromString(new_org); 1606 newOrgOk = true; 1607 } catch (IllegalArgumentException iae) { 1608 newOrgOk = false; 1609 } 1610 1611 if ((new_name == null) || (new_name.length() <= 0)) { 1612 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") 1613 + "Please fill in a name.. hit the Back button and try again.</div>"); 1614 } else if ((new_email == null) || (new_email.length() <= 0) 1615 || ((-1 == new_email.indexOf('@')) || (-1 == new_email.indexOf('.')))) { 1616 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") 1617 + "Please fill in an <b>email</b>.. hit the Back button and try again.</div>"); 1618 } else if (newOrgOk == false) { 1619 ctx.println("<div class='sterrmsg'>" 1620 + ctx.iconHtml("stop", "Could not add user") 1621 + "That Organization (<b>" 1622 + new_org 1623 + "</b>) is not valid. Either it is not spelled properly, or someone must update VoteResolver.Organization in VoteResolver.java</div>"); 1624 } else if ((new_org == null) || (new_org.length() <= 0)) { // for ADMIN 1625 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") 1626 + "Please fill in an <b>Organization</b>.. hit the Back button and try again.</div>"); 1627 } else if (new_userlevel < 0) { 1628 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") 1629 + "Please fill in a <b>user level</b>.. hit the Back button and try again.</div>"); 1630 } else if (new_userlevel == UserRegistry.EXPERT && ctx.session.user.userlevel != UserRegistry.ADMIN) { 1631 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") 1632 + "Only Admin can create EXPERT users.. hit the Back button and try again.</div>"); 1633 } else { 1634 UserRegistry.User u = reg.getEmptyUser(); 1635 1636 u.name = new_name; 1637 u.userlevel = UserRegistry.userCanCreateUserOfLevel(ctx.session.user, new_userlevel); 1638 u.email = new_email; 1639 u.org = new_org; 1640 u.locales = new_locales; 1641 u.password = UserRegistry.makePassword(u.email + u.org + ctx.session.user.email); 1642 1643 SurveyLog.debug("UR: Attempt newuser by " + ctx.session.user.email + ": of " + u.email + " @ " + ctx.userIP()); 1644 UserRegistry.User registeredUser = reg.newUser(ctx, u); 1645 1646 if (registeredUser == null) { 1647 if (reg.get(new_email) != null) { // already exists.. 1648 ctx.println("<div class='sterrmsg'>" 1649 + ctx.iconHtml("stop", "Could not add user") 1650 + "A user with that email already exists. If you have permission, you may be able to edit this user: <tt>"); 1651 printUserZoomLink(ctx, new_email, new_email); 1652 ctx.println("</tt> </div>"); 1653 } else { 1654 ctx.println("<div class='sterrmsg'>" + ctx.iconHtml("stop", "Could not add user") + "Couldn't add user <tt>" 1655 + new_email + "</tt> - an unknown error occured.</div>"); 1656 } 1657 } else { 1658 ctx.println("<i>" + ctx.iconHtml("okay", "added") + "user added.</i>"); 1659 new_email = registeredUser.email.toLowerCase(); 1660 WebContext nuCtx = (WebContext) ctx.clone(); 1661 nuCtx.addQuery(QUERY_DO, "list"); 1662 nuCtx.addQuery(LIST_JUST, URLEncoder.encode(new_email)); 1663 ctx.println("" + "<form action='" + ctx.base() + "' method='POST'>"); 1664 ctx.print("<input name='s' type='hidden' value='" + ctx.session.id + "'/>" 1665 + "<input name='justu' type='hidden' value='" + new_email + "'/>" 1666 + "<input name='do' type='hidden' value='list'/>" + "<input name='" + registeredUser.id + "_" + new_email 1667 + "' type='hidden' value='sendpassword_'/>" 1668 + "<label><input type='submit' value='Send Password Email to " + new_email + "'/>" 1669 + ctx.iconHtml("warn", "Note..") 1670 + "The password is not sent to the user automatically. <b>You must click this button!!</b></label>" 1671 + "</form>" + 1672 1673 "<br>Click here to manage this user: '<b><a href='" + nuCtx.url() + "#u_" + u.email + "'>" 1674 + ctx.iconHtml("zoom", "Zoom in on user") + "manage " + new_name + "</a></b>' page.</p>"); 1675 ctx.print("<br>Their login link is: "); 1676 registeredUser.printPasswordLink(ctx); 1677 ctx.println(" (clicking this will log you in as them.)<br>"); 1678 } 1679 } 1680 1681 printFooter(ctx); 1682 } 1683 showAddUser(WebContext ctx)1684 private void showAddUser(WebContext ctx) { 1685 reg.setOrgList(); // setup the list of orgs 1686 String defaultorg = ""; 1687 1688 if (!UserRegistry.userIsAdmin(ctx.session.user)) { 1689 defaultorg = URLEncoder.encode(ctx.session.user.org); 1690 } 1691 1692 ctx.println("<br><a href='" + ctx.jspLink("adduser.jsp") + "&defaultorg=" + defaultorg + "'>Add User</a> |"); 1693 } 1694 1695 /** 1696 * 1697 * @param ctx 1698 * 1699 * This function is 356 lines long 1700 */ doCoverage(WebContext ctx)1701 private void doCoverage(WebContext ctx) { 1702 boolean showCodes = false; 1703 printHeader(ctx, "Locale Coverage"); 1704 1705 if (!UserRegistry.userIsVetter(ctx.session.user)) { 1706 ctx.print("Not authorized."); 1707 return; 1708 } 1709 1710 printUserTableWithHelp(ctx, "/LocaleCoverage"); 1711 1712 showAddUser(ctx); 1713 1714 ctx.println(" <i>Showing only votes in the current release</i><br/>"); 1715 ctx.print("<br>"); 1716 ctx.println("<a href='" + ctx.url() + "'><b>SurveyTool in</b></a><hr>"); 1717 String org = ctx.session.user.org; 1718 if (UserRegistry.userCreateOtherOrgs(ctx.session.user)) { 1719 org = null; // all 1720 } 1721 1722 StandardCodes sc = StandardCodes.make(); 1723 1724 LocaleTree tree = getLocaleTree(); 1725 1726 WebContext subCtx = (WebContext) ctx.clone(); 1727 subCtx.setQuery(QUERY_DO, "coverage"); 1728 boolean participation = showTogglePref(subCtx, "cov_participation", "Participation Shown (click to toggle)"); 1729 String missingLocalesForOrg = org; 1730 if (missingLocalesForOrg == null) { 1731 missingLocalesForOrg = showListPref(subCtx, PREF_COVTYP, "Coverage Type", WebContext.getLocaleCoverageOrganizations(), true); 1732 } 1733 if (missingLocalesForOrg == null || missingLocalesForOrg.length() == 0 || missingLocalesForOrg.equals("default")) { 1734 missingLocalesForOrg = "default"; // ?! 1735 } 1736 1737 if (org == null) { 1738 ctx.println("<h4>Showing coverage for all organizations</h4>"); 1739 } else { 1740 ctx.println("<h4>Showing coverage for: " + org + "</h4>"); 1741 } 1742 1743 if (missingLocalesForOrg != org) { 1744 ctx.println("<h4> (and missing locales for " + missingLocalesForOrg + ")</h4>"); 1745 } 1746 1747 /* 1748 * TODO: remove this call to getInFiles unless it has a required side-effect 1749 */ 1750 getInFiles(); 1751 Set<CLDRLocale> allLocs = SurveyMain.getLocalesSet(); 1752 int totalUsers = 0; 1753 int allUsers = 0; // users with all 1754 1755 int totalSubmit = 0; 1756 int totalVet = 0; 1757 1758 Map<CLDRLocale, Set<CLDRLocale>> intGroups = getIntGroups(); 1759 1760 Connection conn = null; 1761 Map<String, String> userMap = null; 1762 Map<String, String> nullMap = null; 1763 Hashtable<CLDRLocale, Hashtable<Integer, String>> localeStatus = null; 1764 Hashtable<CLDRLocale, Hashtable<Integer, String>> nullStatus = null; 1765 1766 { 1767 userMap = new TreeMap<>(); 1768 nullMap = new TreeMap<>(); 1769 localeStatus = new Hashtable<>(); 1770 nullStatus = new Hashtable<>(); 1771 } 1772 1773 Set<CLDRLocale> s = new TreeSet<>(); 1774 Set<CLDRLocale> badSet = new TreeSet<>(); 1775 PreparedStatement psMySubmit = null; 1776 PreparedStatement psnSubmit = null; 1777 1778 try { 1779 conn = dbUtils.getDBConnection(); 1780 psMySubmit = conn.prepareStatement("select COUNT(submitter) from " + DBUtils.Table.VOTE_VALUE + " where submitter=?", 1781 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 1782 psnSubmit = conn.prepareStatement( 1783 "select COUNT(submitter) from " + DBUtils.Table.VOTE_VALUE + " where submitter=? and locale=?", 1784 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 1785 1786 synchronized (reg) { 1787 java.sql.ResultSet rs = reg.list(org, conn); 1788 if (rs == null) { 1789 ctx.println("<i>No results...</i>"); 1790 return; 1791 } 1792 if (UserRegistry.userCreateOtherOrgs(ctx.session.user)) { 1793 org = "ALL"; // all 1794 } 1795 while (rs.next()) { 1796 int theirId = rs.getInt(1); 1797 int theirLevel = rs.getInt(2); 1798 String theirName = DBUtils.getStringUTF8(rs, 3);// rs.getString(3); 1799 String theirEmail = rs.getString(4); 1800 //String theirOrg = rs.getString(5); 1801 String theirLocaleList = rs.getString(6); 1802 1803 String nameLink = "<a href='" + ctx.url() + ctx.urlConnector() + "do=list&" + LIST_JUST + "=" 1804 + URLEncoder.encode(theirEmail) + "' title='More on this user...'>" + theirName + " </a>"; 1805 // setup 1806 1807 if (participation && (conn != null)) { 1808 psMySubmit.setInt(1, theirId); 1809 psnSubmit.setInt(1, theirId); 1810 1811 int mySubmit = DBUtils.sqlCount(ctx, conn, psMySubmit); 1812 1813 String userInfo = "<tr><td>" + nameLink + "</td><td>" + "submits/votes: " + mySubmit + "</td></tr>"; 1814 if ((mySubmit) == 0) { 1815 nullMap.put(theirName, userInfo); 1816 } else { 1817 userMap.put(theirName, userInfo); 1818 } 1819 1820 totalSubmit += mySubmit; 1821 } 1822 if ((theirLevel > 10) || (theirLevel <= 1)) { 1823 continue; 1824 } 1825 totalUsers++; 1826 if ((theirLocaleList == null) || theirLocaleList.length() == 0) { 1827 allUsers++; 1828 continue; 1829 } 1830 if (UserRegistry.isAllLocales(theirLocaleList)) { 1831 // all. 1832 allUsers++; 1833 } else { 1834 CLDRLocale theirLocales[] = UserRegistry.tokenizeCLDRLocale(theirLocaleList); 1835 // int hitList[] = new int[theirLocales.length]; // # of 1836 // times each is used 1837 Set<CLDRLocale> theirSet = new HashSet<>(); // set 1838 // of 1839 // locales 1840 // this 1841 // vetter 1842 // has 1843 // access 1844 // to 1845 for (int j = 0; j < theirLocales.length; j++) { 1846 Set<CLDRLocale> subSet = intGroups.get(theirLocales[j]); // Is 1847 // it 1848 // an 1849 // interest 1850 // group? 1851 // (de, 1852 // fr, 1853 // ..) 1854 if (subSet != null) { 1855 theirSet.addAll(subSet); // add all sublocs 1856 } else if (allLocs.contains(theirLocales[j])) { 1857 theirSet.add(theirLocales[j]); 1858 } else { 1859 badSet.add(theirLocales[j]); 1860 } 1861 } 1862 for (CLDRLocale theLocale : theirSet) { 1863 s.add(theLocale); 1864 Hashtable<CLDRLocale, Hashtable<Integer, String>> theHash = localeStatus; // to 1865 // the 1866 // 'status' 1867 // field 1868 String userInfo = nameLink + " "; 1869 if (participation && conn != null) { 1870 psnSubmit.setString(2, theLocale.getBaseName()); 1871 1872 int nSubmit = DBUtils.sqlCount(ctx, conn, psnSubmit); 1873 1874 if ((nSubmit) == 0) { 1875 theHash = nullStatus; // vetter w/ no work 1876 // done 1877 } 1878 1879 if (nSubmit > 0) { 1880 userInfo = userInfo + " submits: " + nSubmit + " "; 1881 } 1882 } 1883 Hashtable<Integer, String> oldStr = theHash.get(theLocale); 1884 1885 if (oldStr == null) { 1886 oldStr = new Hashtable<>(); 1887 theHash.put(theLocale, oldStr); 1888 } 1889 1890 oldStr.put(new Integer(theirId), userInfo + "<!-- " + theLocale + " -->"); 1891 1892 } 1893 } 1894 } 1895 // #level $name $email $org 1896 rs.close(); 1897 } /* end synchronized(reg) */ 1898 } catch (SQLException se) { 1899 SurveyLog.logger.log(java.util.logging.Level.WARNING, 1900 "Query for org " + org + " failed: " + DBUtils.unchainSqlException(se), se); 1901 ctx.println("<i>Failure: " + DBUtils.unchainSqlException(se) + "</i><br>"); 1902 } finally { 1903 DBUtils.close(psMySubmit, psnSubmit, conn); 1904 } 1905 1906 // Now, calculate coverage of requested locales for this organization 1907 Set<CLDRLocale> languagesNotInCLDR = new TreeSet<>(); 1908 Set<CLDRLocale> languagesMissing = new HashSet<>(); 1909 Set<CLDRLocale> allLanguages = new TreeSet<>(); 1910 { 1911 for (String code : sc.getAvailableCodes("language")) { 1912 allLanguages.add(CLDRLocale.getInstance(code)); 1913 } 1914 } 1915 for (Iterator<CLDRLocale> li = allLanguages.iterator(); li.hasNext();) { 1916 CLDRLocale lang = (li.next()); 1917 String group = sc.getGroup(lang.getBaseName(), missingLocalesForOrg); 1918 if ((group != null) && 1919 (null == getSupplementalDataInfo().getBaseFromDefaultContent(CLDRLocale.getInstance(group)))) { 1920 if (!isValidLocale(lang)) { 1921 languagesNotInCLDR.add(lang); 1922 } else { 1923 if (!s.contains(lang)) { 1924 languagesMissing.add(lang); 1925 } 1926 } 1927 } 1928 } 1929 1930 ctx.println("Locales in <b>bold</b> have assigned vetters.<br><table summary='Locale Coverage' border=1 class='list'>"); 1931 int n = 0; 1932 for (String ln : tree.getTopLocales()) { 1933 n++; 1934 CLDRLocale aLocale = tree.getLocaleCode(ln); 1935 ctx.print("<tr class='row" + (n % 2) + "'>"); 1936 ctx.print(" <td valign='top'>"); 1937 boolean has = (s.contains(aLocale)); 1938 if (has) { 1939 ctx.print("<span class='selected'>"); 1940 } else { 1941 ctx.print("<span class='disabledbox' style='color:#888'>"); 1942 } 1943 ctx.print(decoratedLocaleName(aLocale, ln.toString(), aLocale.toString())); 1944 ctx.print("</span>"); 1945 if (languagesMissing.contains(aLocale)) { 1946 ctx.println("<br>" + ctx.iconHtml("stop", "No " + missingLocalesForOrg + " vetters") + "<i>(coverage: " 1947 + sc.getGroup(aLocale.toString(), missingLocalesForOrg) + ")</i>"); 1948 } 1949 1950 if (showCodes) { 1951 ctx.println("<br><tt>" + aLocale + "</tt>"); 1952 } 1953 if (localeStatus != null && !localeStatus.isEmpty()) { 1954 Hashtable<Integer, String> what = localeStatus.get(aLocale); 1955 if (what != null) { 1956 ctx.println("<ul>"); 1957 for (Iterator<String> i = what.values().iterator(); i.hasNext();) { 1958 ctx.println("<li>" + i.next() + "</li>"); 1959 } 1960 ctx.println("</ul>"); 1961 } 1962 } 1963 boolean localeIsDefaultContent = getSupplementalDataInfo().isDefaultContent(aLocale); 1964 if (localeIsDefaultContent) { 1965 ctx.println(" (<i>default content</i>)"); 1966 } else if (participation && nullStatus != null && !nullStatus.isEmpty()) { 1967 Hashtable<Integer, String> what = nullStatus.get(aLocale); 1968 if (what != null) { 1969 ctx.println("<br><blockquote> <b>Did not participate:</b> "); 1970 for (Iterator<String> i = what.values().iterator(); i.hasNext();) { 1971 ctx.println(i.next().toString()); 1972 if (i.hasNext()) { 1973 ctx.println(", "); 1974 } 1975 } 1976 ctx.println("</blockquote>"); 1977 } 1978 } 1979 ctx.println(" </td>"); 1980 1981 Map<String, CLDRLocale> sm = tree.getSubLocales(aLocale); // sub 1982 // locales 1983 1984 ctx.println("<td valign='top'>"); 1985 int j = 0; 1986 for (Iterator<String> si = sm.keySet().iterator(); si.hasNext();) { 1987 String sn = si.next().toString(); 1988 CLDRLocale subLocale = sm.get(sn); 1989 1990 has = (s.contains(subLocale)); 1991 1992 if (j > 0) { 1993 if (localeStatus == null) { 1994 ctx.println(", "); 1995 } else { 1996 ctx.println("<br>"); 1997 } 1998 } 1999 2000 if (has) { 2001 ctx.print("<span class='selected'>"); 2002 } else { 2003 ctx.print("<span class='disabledbox' style='color:#888'>"); 2004 } 2005 ctx.print(decoratedLocaleName(CLDRLocale.getInstance(subLocale.toString()), sn, subLocale.toString())); 2006 ctx.print("</span>"); 2007 if (showCodes) { 2008 ctx.println(" - <tt>" + subLocale + "</tt>"); 2009 } 2010 boolean isDc = getSupplementalDataInfo().isDefaultContent(subLocale); 2011 2012 if (localeStatus != null && !nullStatus.isEmpty()) { 2013 Hashtable<Integer, String> what = localeStatus.get(subLocale); 2014 if (what != null) { 2015 ctx.println("<ul>"); 2016 for (Iterator<String> i = what.values().iterator(); i.hasNext();) { 2017 ctx.println("<li>" + i.next() + "</li>"); 2018 } 2019 ctx.println("</ul>"); 2020 } 2021 } 2022 if (isDc) { 2023 ctx.println(" (<i>default content</i>)"); 2024 } 2025 j++; 2026 } 2027 ctx.println("</td>"); 2028 ctx.println("</tr>"); 2029 } 2030 ctx.println("</table> "); 2031 ctx.println(totalUsers + " users, including " + allUsers + " with 'all' privs (not counted against the locale list)<br>"); 2032 2033 if (conn != null) { 2034 if (participation) { 2035 ctx.println("Selected users have submitted " + totalSubmit + " items, and voted for " + totalVet 2036 + " items (including implied votes).<br>"); 2037 } 2038 if (participation) { 2039 ctx.println("<hr>"); 2040 ctx.println("<h4>Participated: " + userMap.size() + "</h4><table border='1'>"); 2041 for (Iterator<String> i = userMap.values().iterator(); i.hasNext();) { 2042 String which = i.next(); 2043 ctx.println(which); 2044 } 2045 ctx.println("</table><h4>Did Not Participate at all: " + nullMap.size() + "</h4><table border='1'>"); 2046 for (Iterator<String> i = nullMap.values().iterator(); i.hasNext();) { 2047 String which = i.next(); 2048 ctx.println(which); 2049 } 2050 ctx.println("</table>"); 2051 } 2052 DBUtils.closeDBConnection(conn); 2053 } 2054 2055 printFooter(ctx); 2056 } 2057 2058 // ============= User list management 2059 private static final String LIST_ACTION_SETLEVEL = "set_userlevel_"; 2060 private static final String LIST_ACTION_NONE = "-"; 2061 private static final String LIST_ACTION_SHOW_PASSWORD = "showpassword_"; 2062 private static final String LIST_ACTION_SEND_PASSWORD = "sendpassword_"; 2063 private static final String LIST_ACTION_SETLOCALES = "set_locales_"; 2064 private static final String LIST_ACTION_DELETE0 = "delete0_"; 2065 private static final String LIST_ACTION_DELETE1 = "delete_"; 2066 private static final String LIST_JUST = "justu"; 2067 private static final String LIST_MAILUSER = "mailthem"; 2068 private static final String LIST_MAILUSER_WHAT = "mailthem_t"; 2069 private static final String LIST_MAILUSER_CONFIRM = "mailthem_c"; 2070 private static final String LIST_MAILUSER_CONFIRM_CODE = "confirm"; 2071 doUDump(WebContext ctx)2072 private void doUDump(WebContext ctx) { 2073 ctx.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); 2074 ctx.println("<users host=\"" + ctx.serverHostport() + "\">"); 2075 String org = null; 2076 Connection conn = null; 2077 try { 2078 conn = dbUtils.getDBConnection(); 2079 synchronized (reg) { 2080 java.sql.ResultSet rs = reg.list(org, conn); 2081 if (rs == null) { 2082 ctx.println("\t<!-- No results -->"); 2083 return; 2084 } 2085 while (rs.next()) { 2086 int theirId = rs.getInt(1); 2087 int theirLevel = rs.getInt(2); 2088 String theirName = DBUtils.getStringUTF8(rs, 3);// rs.getString(3); 2089 String theirEmail = rs.getString(4); 2090 String theirOrg = rs.getString(5); 2091 String theirLocales = rs.getString(6); 2092 2093 ctx.println("\t<user id=\"" + theirId + "\" email=\"" + theirEmail + "\">"); 2094 ctx.println("\t\t<level n=\"" + theirLevel + "\" type=\"" + UserRegistry.levelAsStr(theirLevel) + "\"/>"); 2095 ctx.println("\t\t<name>" + theirName + "</name>"); 2096 ctx.println("\t\t<org>" + theirOrg + "</org>"); 2097 ctx.println("\t\t<locales type=\"edit\">"); 2098 Set<CLDRLocale> locs = UserRegistry.tokenizeValidCLDRLocale(theirLocales); 2099 for (CLDRLocale loc : locs) { 2100 ctx.println("\t\t\t<locale id=\"" + loc.getBaseName() + "\"/>"); 2101 } 2102 ctx.println("\t\t</locales>"); 2103 ctx.println("\t</user>"); 2104 } 2105 } /* end synchronized(reg) */ 2106 } catch (SQLException se) { 2107 SurveyLog.logger.log(java.util.logging.Level.WARNING, 2108 "Query for org " + org + " failed: " + DBUtils.unchainSqlException(se), se); 2109 ctx.println("<!-- Failure: " + DBUtils.unchainSqlException(se) + " -->"); 2110 } finally { 2111 DBUtils.close(conn); 2112 } 2113 ctx.println("</users>"); 2114 } 2115 2116 /** 2117 * List Users 2118 * 2119 * @param ctx 2120 * 2121 * TODO: this function is over 666 lines long. Shorten it with subroutines. 2122 */ doList(WebContext ctx)2123 private void doList(WebContext ctx) { 2124 int n = 0; 2125 String just = ctx.field(LIST_JUST); 2126 String doWhat = ctx.field(QUERY_DO); 2127 boolean justme = false; // "my account" mode 2128 String listName = "list"; 2129 if (just.length() == 0) { 2130 just = null; 2131 } else { 2132 justme = ctx.session.user.email.equals(just); 2133 } 2134 if (doWhat.equals("listu")) { 2135 listName = "listu"; 2136 just = ctx.session.user.email; 2137 justme = true; 2138 } 2139 WebContext subCtx = new WebContext(ctx); 2140 subCtx.setQuery(QUERY_DO, doWhat); 2141 if (justme) { 2142 printHeader(ctx, "My Account"); 2143 } else { 2144 printHeader(ctx, "List Users" + ((just == null) ? "" : (" - " + just))); 2145 } 2146 2147 printUserTableWithHelp(ctx, "/AddModifyUser"); 2148 ctx.print(" | "); 2149 printMenu(ctx, doWhat, "coverage", "Show Vetting Participation", QUERY_DO); 2150 2151 if (UserRegistry.userIsTC(ctx.session.user)) { 2152 ctx.println("| <a class='notselected' href='v#tc-emaillist'>Email Address of Users Who Participated</a>"); 2153 ctx.print(" | "); 2154 } 2155 2156 if (UserRegistry.userCanCreateUsers(ctx.session.user)) { 2157 showAddUser(ctx); 2158 } 2159 ctx.print("<br>"); 2160 ctx.println("<a href='" + ctx.url() + "'><b>SurveyTool main</b></a><hr>"); 2161 String org = ctx.session.user.org; 2162 if (just != null) { 2163 ctx.println("<a href='" + ctx.url() + ctx.urlConnector() + "do=list&p_justorg='>\u22d6 Show all users</a><br>"); 2164 } 2165 if (UserRegistry.userIsAdmin(ctx.session.user)) { 2166 if (just == null) { // show a filter 2167 String list0[] = UserRegistry.getOrgList(); 2168 String list1[] = new String[list0.length + 1]; 2169 System.arraycopy(list0, 0, list1, 1, list0.length); 2170 list1[0] = "Show All"; 2171 org = showListSetting(subCtx, "p_justorg", "Filter Organization", list1, true); 2172 if (org.equals(list1[0])) { 2173 org = null; 2174 } 2175 } else { 2176 org = null; // all 2177 } 2178 } 2179 String sendWhat = ctx.field(LIST_MAILUSER_WHAT); 2180 boolean areSendingMail = false; 2181 boolean didConfirmMail = false; 2182 boolean showLocked = ctx.prefBool(PREF_SHOWLOCKED); 2183 // sending a dispute note? 2184 boolean areSendingDisp = (ctx.field(LIST_MAILUSER + "_d").length()) > 0; 2185 String mailBody = null; 2186 String mailSubj = null; 2187 boolean hideUserList = false; 2188 if (UserRegistry.userCanEmailUsers(ctx.session.user)) { 2189 if (ctx.field(LIST_MAILUSER_CONFIRM).equals(LIST_MAILUSER_CONFIRM_CODE)) { 2190 ctx.println("<h1>sending mail to users...</h4>"); 2191 didConfirmMail = true; 2192 mailBody = "SurveyTool Message ---\n" + sendWhat 2193 + "\n--------\n\nSurvey Tool: http://st.unicode.org" + ctx.base() + "\n\n"; 2194 mailSubj = "CLDR SurveyTool message from " + ctx.session.user.name; 2195 if (!areSendingDisp) { 2196 areSendingMail = true; // we are ready to go ahead and mail.. 2197 } 2198 } else if (ctx.hasField(LIST_MAILUSER_CONFIRM)) { 2199 ctx.println("<h1 class='ferrbox'>" + ctx.iconHtml("stop", "emails did not match") 2200 + " not sending mail - you did not confirm the email address. See form at bottom of page." + "</h1>"); 2201 } 2202 2203 if (!areSendingMail && !areSendingDisp && ctx.hasField(LIST_MAILUSER)) { 2204 hideUserList = true; // hide the user list temporarily. 2205 } 2206 } 2207 Connection conn = null; 2208 try { 2209 conn = dbUtils.getDBConnection(); 2210 synchronized (reg) { 2211 java.sql.ResultSet rs = reg.list(org, conn); 2212 if (rs == null) { 2213 ctx.println("<i>No results...</i>"); 2214 return; 2215 } 2216 if (org == null) { 2217 org = "ALL"; // all 2218 } 2219 if (justme) { 2220 ctx.println("<h2>My Account</h2>"); 2221 } else { 2222 ctx.println("<h2>Users for " + org + "</h2>"); 2223 if (UserRegistry.userIsTC(ctx.session.user)) { 2224 showTogglePref(subCtx, PREF_SHOWLOCKED, "Show locked users:"); 2225 } 2226 ctx.println("<br>"); 2227 if (UserRegistry.userCanModifyUsers(ctx.session.user)) { 2228 ctx.println("<div class='fnotebox'>" 2229 + "Changing user level or locales while a user is active will result in " 2230 + " destruction of their session. Check if they have been working recently.</div>"); 2231 } 2232 } 2233 // Preset box 2234 boolean preFormed = false; 2235 2236 if (hideUserList) { 2237 String warnHash = "userlist"; 2238 ctx.println("<div id='h_" + warnHash + "'><a href='javascript:show(\"" + warnHash + "\")'>" 2239 + "<b>+</b> Click here to show the user list...</a></div>"); 2240 ctx.println("<!-- <noscript>Warning: </noscript> -->" + "<div style='display: none' id='" + warnHash + "'>"); 2241 ctx.println("<a href='javascript:hide(\"" + warnHash + "\")'>" + "(<b>- hide userlist</b>)</a><br>"); 2242 2243 } 2244 2245 if ((just == null) && UserRegistry.userCanModifyUsers(ctx.session.user) && !justme) { 2246 ctx.println("<div class='pager' style='align: right; float: right; margin-left: 4px;'>"); 2247 ctx.println("<form method=POST action='" + ctx.base() + "'>"); 2248 ctx.printUrlAsHiddenFields(); 2249 ctx.println("Set menus:<br><label>all "); 2250 ctx.println("<select name='preset_from'>"); 2251 ctx.println(" <option>" + LIST_ACTION_NONE + "</option>"); 2252 for (int i = 0; i < UserRegistry.ALL_LEVELS.length; i++) { 2253 ctx.println("<option class='user" + UserRegistry.ALL_LEVELS[i] + "' "); 2254 ctx.println(" value='" + UserRegistry.ALL_LEVELS[i] + "'>" 2255 + UserRegistry.levelToStr(ctx, UserRegistry.ALL_LEVELS[i]) + "</option>"); 2256 } 2257 ctx.println("</select></label> <br>"); 2258 ctx.println(" <label>to"); 2259 ctx.println("<select name='preset_do'>"); 2260 ctx.println(" <option>" + LIST_ACTION_NONE + "</option>"); 2261 2262 ctx.println(" <option value='" + LIST_ACTION_SHOW_PASSWORD + "'>Show password URL...</option>"); 2263 ctx.println(" <option value='" + LIST_ACTION_SEND_PASSWORD + "'>Resend password...</option>"); 2264 ctx.println("</select></label> <br>"); 2265 ctx.println("<input type='submit' name='do' value='" + listName + "'></form>"); 2266 if ((ctx.field("preset_from").length() > 0) && !ctx.field("preset_from").equals(LIST_ACTION_NONE)) { 2267 ctx.println("<hr><i><b>Menus have been pre-filled. <br> Confirm your choices and click Change.</b></i>"); 2268 ctx.println("<form method=POST action='" + ctx.base() + "'>"); 2269 ctx.println("<input type='submit' name='doBtn' value='Change'>"); 2270 preFormed = true; 2271 } 2272 ctx.println("</div>"); 2273 } 2274 int preset_fromint = ctx.fieldInt("preset_from", -1); 2275 String preset_do = ctx.field("preset_do"); 2276 if (preset_do.equals(LIST_ACTION_NONE)) { 2277 preset_do = "nothing"; 2278 } 2279 if (/* (just==null)&& */((UserRegistry.userCanModifyUsers(ctx.session.user))) && !preFormed) { // form 2280 // was 2281 // already 2282 // started, 2283 // above 2284 ctx.println("<form method=POST action='" + ctx.base() + "'>"); 2285 } 2286 if (just != null) { 2287 ctx.print("<input type='hidden' name='" + LIST_JUST + "' value='" + just + "'>"); 2288 } 2289 if (justme || UserRegistry.userCanModifyUsers(ctx.session.user)) { 2290 ctx.printUrlAsHiddenFields(); 2291 ctx.println("<input type='hidden' name='do' value='" + listName + "'>"); 2292 ctx.println("<input type='submit' name='doBtn' value='Do Action'>"); 2293 } 2294 ctx.println("<table id='userListTable' summary='User List' class='userlist' border='2'>"); 2295 ctx.println( 2296 "<thead> <tr><th></th><th>Organization / Level</th><th>Name/Email</th><th>Action</th><th>Locales</th><th>Seen</th></tr></thead><tbody>"); 2297 String oldOrg = null; 2298 int locked = 0; 2299 JSONArray shownUsers = new JSONArray(); 2300 while (rs.next()) { 2301 int theirId = rs.getInt(1); 2302 int theirLevel = rs.getInt(2); 2303 /* 2304 * In this context always silently skip anonymous users. Don't send email to anon20@example.org. 2305 * This interface could be changed to treat anonymous users more like locked users, if there is 2306 * ever motivation; but anonymous users should never be sent email. 2307 * Reference: https://unicode.org/cldr/trac/ticket/11517 2308 */ 2309 if (theirLevel == UserRegistry.ANONYMOUS) { 2310 continue; 2311 } 2312 if (!showLocked 2313 && theirLevel >= UserRegistry.LOCKED 2314 && just == null /* if only one user, show regardless of lock state. */) { 2315 locked++; 2316 continue; 2317 } 2318 String theirName = DBUtils.getStringUTF8(rs, 3);// rs.getString(3); 2319 String theirEmail = rs.getString(4); 2320 String theirOrg = rs.getString(5); 2321 String theirLocales = rs.getString(6); 2322 java.sql.Timestamp theirLast = rs.getTimestamp(8); 2323 boolean havePermToChange = ctx.session.user.isAdminFor(reg.getInfo(theirId)); 2324 2325 String theirTag = theirId + "_" + theirEmail; // ID+email - 2326 // prevents 2327 // stale 2328 // data. 2329 // (i.e. 2330 // delete of 2331 // user 3 if 2332 // the rows 2333 // change..) 2334 String action = ctx.field(theirTag); 2335 CookieSession theUser = CookieSession.retrieveUserWithoutTouch(theirEmail); 2336 2337 if (just != null && !just.equals(theirEmail)) { 2338 continue; 2339 } 2340 n++; 2341 2342 shownUsers.put(reg.getInfo(theirId)); 2343 2344 if ((just == null) && (!justme) && (!theirOrg.equals(oldOrg))) { 2345 ctx.println("<tr class='heading' ><th class='partsection' colspan='6'><a name='" + theirOrg + "'><h4>" 2346 + theirOrg + "</h4></a></th></tr>"); 2347 oldOrg = theirOrg; 2348 } 2349 2350 ctx.println(" <tr id='u@" + theirId + "' class='user" + theirLevel + "'>"); 2351 2352 if (areSendingMail && (theirLevel < UserRegistry.LOCKED)) { 2353 ctx.print("<td class='framecell'>"); 2354 MailSender.getInstance().queue(ctx.userId(), theirId, mailSubj, mailBody); 2355 ctx.println("(queued)</td>"); 2356 } 2357 // first: DO. 2358 2359 if (havePermToChange) { // do stuff 2360 2361 String msg = null; 2362 if (ctx.field(LIST_ACTION_SETLOCALES + theirTag).length() > 0) { 2363 ctx.println("<td class='framecell' >"); 2364 String newLocales = ctx.field(LIST_ACTION_SETLOCALES + theirTag); 2365 msg = reg.setLocales(ctx, theirId, theirEmail, newLocales); 2366 ctx.println(msg); 2367 theirLocales = newLocales; // MODIFY 2368 if (theUser != null) { 2369 ctx.println("<br/><i>Logging out user session " + theUser.id 2370 + " and deleting all unsaved changes</i>"); 2371 theUser.remove(); 2372 } 2373 UserRegistry.User newThem = reg.getInfo(theirId); 2374 if (newThem != null) { 2375 theirLocales = newThem.locales; // update 2376 } 2377 ctx.println("</td>"); 2378 } else if ((action != null) && (action.length() > 0) && (!action.equals(LIST_ACTION_NONE))) { // other 2379 // actions 2380 ctx.println("<td class='framecell'>"); 2381 2382 // check an explicit list. Don't allow random levels 2383 // to be set. 2384 for (int i = 0; i < UserRegistry.ALL_LEVELS.length; i++) { 2385 if (action.equals(LIST_ACTION_SETLEVEL + UserRegistry.ALL_LEVELS[i])) { 2386 if ((just == null) && (UserRegistry.ALL_LEVELS[i] <= UserRegistry.TC)) { 2387 ctx.println("<b>Must be zoomed in on a user to promote them to TC</b>"); 2388 } else { 2389 msg = reg.setUserLevel(ctx, theirId, theirEmail, UserRegistry.ALL_LEVELS[i]); 2390 ctx.println("Set user level to " 2391 + UserRegistry.levelToStr(ctx, UserRegistry.ALL_LEVELS[i])); 2392 ctx.println(": " + msg); 2393 theirLevel = UserRegistry.ALL_LEVELS[i]; 2394 if (theUser != null) { 2395 ctx.println("<br/><i>Logging out user session " + theUser.id + "</i>"); 2396 theUser.remove(); 2397 } 2398 } 2399 } 2400 } 2401 2402 if (action.equals(LIST_ACTION_SHOW_PASSWORD)) { 2403 String pass = reg.getPassword(ctx, theirId); 2404 if (pass != null) { 2405 UserRegistry.printPasswordLink(ctx, theirEmail, pass); 2406 ctx.println(" <tt class='winner'>" + pass + "</tt>"); 2407 } 2408 } else if (action.equals(LIST_ACTION_SEND_PASSWORD)) { 2409 String pass = reg.getPassword(ctx, theirId); 2410 if (pass != null && theirLevel < UserRegistry.LOCKED) { 2411 UserRegistry.printPasswordLink(ctx, theirEmail, pass); 2412 notifyUser(ctx, theirEmail, pass); 2413 } 2414 } else if (action.equals(LIST_ACTION_DELETE0)) { 2415 ctx.println("Ensure that 'confirm delete' is chosen at right and click Do Action to delete.."); 2416 } else if ((UserRegistry.userCanDeleteUser(ctx.session.user, theirId, theirLevel)) 2417 && (action.equals(LIST_ACTION_DELETE1))) { 2418 msg = reg.delete(ctx, theirId, theirEmail); 2419 ctx.println("<strong style='font-color: red'>Deleting...</strong><br>"); 2420 ctx.println(msg); 2421 } else if ((UserRegistry.userCanModifyUser(ctx.session.user, theirId, theirLevel)) 2422 && (action.equals(LIST_ACTION_SETLOCALES))) { 2423 if (theirLocales == null) { 2424 theirLocales = ""; 2425 } 2426 ctx.println("<label>Locales: (space separated) <input id='" + LIST_ACTION_SETLOCALES + theirTag + "' name='" 2427 + LIST_ACTION_SETLOCALES + theirTag 2428 + "' value='" + theirLocales + "'></label>"); 2429 ctx.println("<button onclick=\"{document.getElementById('" + LIST_ACTION_SETLOCALES + theirTag 2430 + "').value='*'; return false;}\" >All Locales</button>"); 2431 } else if (UserRegistry.userCanDeleteUser(ctx.session.user, theirId, theirLevel)) { 2432 // change of other stuff. 2433 UserRegistry.InfoType type = UserRegistry.InfoType.fromAction(action); 2434 2435 if (UserRegistry.userIsAdmin(ctx.session.user) && type == UserRegistry.InfoType.INFO_PASSWORD) { 2436 String what = "password"; 2437 2438 String s0 = ctx.field("string0" + what); 2439 String s1 = ctx.field("string1" + what); 2440 if (s0.equals(s1) && s0.length() > 0) { 2441 ctx.println("<h4>Change " + what + " to <tt class='codebox'>" + s0 + "</tt></h4>"); 2442 action = ""; // don't popup the menu 2443 // again. 2444 2445 msg = reg.updateInfo(ctx, theirId, theirEmail, type, s0); 2446 ctx.println("<div class='fnotebox'>" + msg + "</div>"); 2447 ctx.println("<i>click Change again to see changes</i>"); 2448 } else { 2449 ctx.println("<h4>Change " + what + "</h4>"); 2450 if (s0.length() > 0) { 2451 ctx.println("<p class='ferrbox'>Both fields must match.</p>"); 2452 } 2453 ctx.println( 2454 "<p role='alert' style='font-size: 1.5em;'><em>PASSWORDS MAY BE VISIBLE AS PLAIN TEXT. USE OF A RANDOM PASSWORD (as suggested) IS STRONGLY RECOMMENDED.</em></p>"); 2455 ctx.println("<label><b>New " + what + ":</b><input type='password' name='string0" + what 2456 + "' value='" + s0 + "'></label><br>"); 2457 ctx.println("<label><b>New " + what + ":</b><input type='password' name='string1" + what 2458 + "'> (confirm)</label>"); 2459 2460 ctx.println("<br><br>"); 2461 ctx.println("(Suggested random password: <tt>" + UserRegistry.makePassword(theirEmail) 2462 + "</tt> )"); 2463 } 2464 } else if (type != null) { 2465 String what = type.toString(); 2466 2467 String s0 = ctx.field("string0" + what); 2468 String s1 = ctx.field("string1" + what); 2469 if (type == InfoType.INFO_ORG) 2470 s1 = s0; /* ignore */ 2471 if (s0.equals(s1) && s0.length() > 0) { 2472 ctx.println("<h4>Change " + what + " to <tt class='codebox'>" + s0 + "</tt></h4>"); 2473 action = ""; // don't popup the menu 2474 // again. 2475 2476 msg = reg.updateInfo(ctx, theirId, theirEmail, type, s0); 2477 ctx.println("<div class='fnotebox'>" + msg + "</div>"); 2478 ctx.println("<i>click Change again to see changes</i>"); 2479 } else { 2480 ctx.println("<h4>Change " + what + "</h4>"); 2481 if (s0.length() > 0) { 2482 ctx.println("<p class='ferrbox'>Both fields must match.</p>"); 2483 } 2484 if (type == InfoType.INFO_ORG) { 2485 ctx.println("<select name='string0" + what + "'>"); 2486 ctx.println("<option value='' >Choose...</option>"); 2487 for (String o : UserRegistry.getOrgList()) { 2488 ctx.print("<option value='" + o + "' "); 2489 if (o.equals(theirOrg)) { 2490 ctx.print(" selected='selected' "); 2491 } 2492 ctx.println(">" + o + "</option>"); 2493 } 2494 ctx.println("</select>"); 2495 } else { 2496 ctx.println("<label><b>New " + what + ":</b><input name='string0" + what 2497 + "' value='" + s0 + "'></label><br>"); 2498 ctx.println("<label><b>New " + what + ":</b><input name='string1" + what 2499 + "'> (confirm)</label>"); 2500 } 2501 } 2502 } 2503 } else if (theirId == ctx.session.user.id) { 2504 ctx.println("<i>You can't change that setting on your own account.</i>"); 2505 } else { 2506 ctx.println("<i>No changes can be made to this user.</i>"); 2507 } 2508 // ctx.println("Change to " + action); 2509 } else { 2510 ctx.print("<td>"); 2511 } 2512 } else { 2513 ctx.print("<td>"); 2514 } 2515 2516 if (just == null) { 2517 printUserZoomLink(ctx, theirEmail, ""); 2518 } 2519 2520 ctx.println("</td>"); 2521 2522 // org, level 2523 ctx.println(" <td>" + theirOrg + "<br>" + " <span style='font-size: 80%' align='right'>" 2524 + UserRegistry.levelToStr(ctx, theirLevel).replaceAll(" ", " ") + "</span></td>"); 2525 2526 ctx.println(" <td valign='top'><font size='-1'>#" + theirId + " </font> <a name='u_" + theirEmail + "'>" 2527 + theirName + "</a>"); 2528 ctx.println(" <a href='mailto:" + theirEmail + "'>" + theirEmail + "</a>"); 2529 ctx.print("</td><td>"); 2530 if (havePermToChange) { 2531 // Was something requested? 2532 2533 { // PRINT MENU 2534 ctx.print("<select name='" + theirTag + "' "); 2535 if (just != null) { 2536 ctx.print(" onchange=\"this.form.submit()\" "); 2537 } 2538 ctx.print(">"); 2539 2540 // set user to VETTER 2541 ctx.println(" <option value=''>" + LIST_ACTION_NONE + "</option>"); 2542 for (int i = 0; i < UserRegistry.ALL_LEVELS.length; i++) { 2543 int lev = UserRegistry.ALL_LEVELS[i]; 2544 if (just == null && lev != UserRegistry.LOCKED) { 2545 continue; // only allow mass LOCK (for now) 2546 } 2547 doChangeUserOption(ctx, lev, theirLevel, false); 2548 } 2549 ctx.println(" <option disabled>" + LIST_ACTION_NONE + "</option>"); 2550 ctx.println(" <option "); 2551 if ((preset_fromint == theirLevel) && preset_do.equals(LIST_ACTION_SHOW_PASSWORD)) { 2552 ctx.println(" SELECTED "); 2553 } 2554 ctx.println(" value='" + LIST_ACTION_SHOW_PASSWORD + "'>Show password...</option>"); 2555 ctx.println(" <option "); 2556 if ((preset_fromint == theirLevel) && preset_do.equals(LIST_ACTION_SEND_PASSWORD)) { 2557 ctx.println(" SELECTED "); 2558 } 2559 ctx.println(" value='" + LIST_ACTION_SEND_PASSWORD + "'>Send password...</option>"); 2560 2561 if (just != null) { 2562 if (havePermToChange) { 2563 ctx.println(" <option "); 2564 ctx.println(" value='" + LIST_ACTION_SETLOCALES + "'>Set locales...</option>"); 2565 } 2566 if (UserRegistry.userCanDeleteUser(ctx.session.user, theirId, theirLevel)) { 2567 ctx.println(" <option>" + LIST_ACTION_NONE + "</option>"); 2568 if ((action != null) && action.equals(LIST_ACTION_DELETE0)) { 2569 ctx.println(" <option value='" + LIST_ACTION_DELETE1 2570 + "' SELECTED>Confirm delete</option>"); 2571 } else { 2572 ctx.println(" <option "); 2573 if ((preset_fromint == theirLevel) && preset_do.equals(LIST_ACTION_DELETE0)) { 2574 // ctx.println(" SELECTED "); 2575 } 2576 ctx.println(" value='" + LIST_ACTION_DELETE0 + "'>Delete user..</option>"); 2577 } 2578 } 2579 if (just != null) { // only do these in 'zoomin' 2580 // view. 2581 ctx.println(" <option disabled>" + LIST_ACTION_NONE + "</option>"); 2582 2583 InfoType current = InfoType.fromAction(action); 2584 for (InfoType info : InfoType.values()) { 2585 if (info == InfoType.INFO_ORG && !(ctx.session.user.userlevel == UserRegistry.ADMIN)) { 2586 continue; 2587 } 2588 ctx.print(" <option "); 2589 if (info == current) { 2590 ctx.print(" SELECTED "); 2591 } 2592 ctx.println(" value='" + info.toAction() + "'>Change " + info.toString() + "...</option>"); 2593 } 2594 } 2595 } 2596 ctx.println(" </select>"); 2597 } // end menu 2598 } 2599 if (ctx.session.user.isAdminFor(reg.getInfo(theirId))) { 2600 ctx.println("<br><a href='" + ctx.context("upload.jsp?s=" + ctx.session.id + "&email=" + theirEmail) 2601 + "'>Upload XML...</a>"); 2602 } 2603 ctx.println("<br><a class='recentActivity' href='" + ctx.context("myvotes.jsp?user=" + theirId) + "'>User Activity</a>"); 2604 ctx.println("</td>"); 2605 2606 if (theirLevel <= UserRegistry.MANAGER) { 2607 ctx.println(" <td>" + UserRegistry.prettyPrintLocale(null) + "</td> "); 2608 } else { 2609 ctx.println(" <td>" + UserRegistry.prettyPrintLocale(theirLocales) + "</td>"); 2610 } 2611 2612 // are they logged in? 2613 if ((theUser != null) && UserRegistry.userCanModifyUsers(ctx.session.user)) { 2614 ctx.println("<td>"); 2615 ctx.println("<b>active: " + timeDiff(theUser.getLastBrowserCallMillisSinceEpoch()) + " ago</b>"); 2616 if (UserRegistry.userIsAdmin(ctx.session.user)) { 2617 ctx.print("<br/>"); 2618 printLiveUserMenu(ctx, theUser); 2619 } 2620 ctx.println("</td>"); 2621 } else if (theirLast != null) { 2622 ctx.println("<td>"); 2623 ctx.println("<b>seen: " + timeDiff(theirLast.getTime()) + " ago</b>"); 2624 ctx.print("<br/><font size='-2'>"); 2625 ctx.print(theirLast.toString()); 2626 ctx.println("</font></td>"); 2627 } 2628 2629 ctx.println(" </tr>"); 2630 } 2631 ctx.println("</tbody></table>"); 2632 2633 // now, serialize the list.. 2634 ctx.println("<script>var shownUsers = " + shownUsers.toString() + ";\r\nshowUserActivity(shownUsers, 'userListTable');\r\n</script>\n"); 2635 2636 if (hideUserList) { 2637 ctx.println("</div>"); 2638 } 2639 if (!justme) { 2640 ctx.println("<div style='font-size: 70%'>Number of users shown: " + n + "</div><br>"); 2641 2642 if (n == 0 && just != null && !just.isEmpty()) { 2643 UserRegistry.User u = reg.get(just); 2644 if (u == null) { 2645 ctx.println("<h3 class='ferrbox'>" + ctx.iconHtml("stop", "Not Found Error") + " User '" + just 2646 + "' does not exist.</h3>"); 2647 } else { 2648 ctx.println("<h3 class='ferrbox'>" + ctx.iconHtml("stop", "Not Found Error") + " User '" + just 2649 + "' from organization " + u.org + " is not visible to you. Ask an administrator.</h3>"); 2650 } 2651 } 2652 2653 if ((UserRegistry.userIsExactlyManager(ctx.session.user) || UserRegistry.userIsTC(ctx.session.user)) 2654 && locked > 0) { 2655 showTogglePref(subCtx, PREF_SHOWLOCKED, "Show " + locked + " locked users:"); 2656 } 2657 } 2658 if (!justme && UserRegistry.userCanModifyUsers(ctx.session.user)) { 2659 if ((n > 0) && UserRegistry.userCanEmailUsers(ctx.session.user)) { 2660 /* 2661 * send a mass email to users 2662 */ 2663 if (ctx.field(LIST_MAILUSER).length() == 0) { 2664 ctx.println("<label><input type='checkbox' value='y' name='" + LIST_MAILUSER 2665 + "'>Check this box to compose a message to these " + n 2666 + " users (excluding LOCKED users).</label>"); 2667 } else { 2668 ctx.println("<p><div class='pager'>"); 2669 ctx.println("<h4>Mailing " + n + " users</h4>"); 2670 if (didConfirmMail) { 2671 if (areSendingDisp) { 2672 throw new InternalError("Not implemented - see DisputePageManager"); 2673 } else { 2674 ctx.println("<b>Mail sent.</b><br>"); 2675 } 2676 } else { // dont' allow resend option 2677 ctx.println("<input type='hidden' name='" + LIST_MAILUSER + "' value='y'>"); 2678 } 2679 ctx.println("From: <b>(depends on recipient organization)</b><br>"); 2680 if (sendWhat.length() > 0) { 2681 ctx.println("<div class='odashbox'>" 2682 + TransliteratorUtilities.toHTML.transliterate(sendWhat).replaceAll("\n", "<br>") 2683 + "</div>"); 2684 if (!didConfirmMail) { 2685 ctx.println("<input type='hidden' name='" + LIST_MAILUSER_WHAT + "' value='" 2686 + sendWhat.replaceAll("&", "&").replaceAll("'", """) + "'>"); 2687 if (!ctx.field(LIST_MAILUSER_CONFIRM).equals(LIST_MAILUSER_CONFIRM_CODE) 2688 && (ctx.field(LIST_MAILUSER_CONFIRM).length() > 0)) { 2689 ctx.println("<strong>" + ctx.iconHtml("stop", "confirmation did not match") 2690 + "That confirmation didn't match. Try again.</strong><br>"); 2691 } 2692 ctx.println("To confirm sending, type the confirmation code <tt class='codebox'>" 2693 + LIST_MAILUSER_CONFIRM_CODE 2694 + "</tt> in this box : <input name='" + LIST_MAILUSER_CONFIRM + "'>"); 2695 } 2696 } else { 2697 ctx.println("<textarea NAME='" + LIST_MAILUSER_WHAT 2698 + "' id='body' ROWS='15' COLS='85' style='width:100%'></textarea>"); 2699 } 2700 ctx.println("</div>"); 2701 } 2702 2703 } 2704 } 2705 // #level $name $email $org 2706 rs.close(); 2707 2708 // more 'My Account' stuff 2709 if (justme) { 2710 ctx.println("<hr>"); 2711 // Is the 'interest locales' list relevant? 2712 if (ctx.session.user.userlevel <= UserRegistry.EXPERT) { 2713 boolean intlocs_change = (ctx.field("intlocs_change").length() > 0); 2714 2715 ctx.println("<h4>Notify me about these locale groups (just the language names, no underscores or dashes):</h4>"); 2716 2717 if (intlocs_change) { 2718 if (ctx.field("intlocs_change").equals("t")) { 2719 String newIntLocs = ctx.field("intlocs"); 2720 2721 String msg = reg.setLocales(ctx, ctx.session.user.id, ctx.session.user.email, newIntLocs, true); 2722 2723 if (msg != null) { 2724 ctx.println(msg + "<br>"); 2725 } 2726 UserRegistry.User newMe = reg.getInfo(ctx.session.user.id); 2727 if (newMe != null) { 2728 ctx.session.user.intlocs = newMe.intlocs; // update 2729 } 2730 } 2731 2732 ctx.println("<input type='hidden' name='intlocs_change' value='t'>"); 2733 ctx.println("<label>Locales: <input name='intlocs' "); 2734 if (ctx.session.user.intlocs != null) { 2735 ctx.println("value='" + ctx.session.user.intlocs.trim() + "' "); 2736 } 2737 ctx.println("</input></label>"); 2738 if (ctx.session.user.intlocs == null) { 2739 ctx.println( 2740 "<br><i>List languages only, separated by spaces. Example: <tt class='codebox'>en fr zh</tt>. leave blank for 'all locales'.</i>"); 2741 } // ctx.println("<br>Note: changing interest locales is currently unimplemented. Check back later.<br>"); 2742 } 2743 2744 ctx.println("<ul><tt class='codebox'>" + UserRegistry.prettyPrintLocale(ctx.session.user.intlocs) 2745 + "</tt>"); 2746 if (!intlocs_change) { 2747 ctx.print("<a href='" + ctx.url() + ctx.urlConnector() + "do=listu&" + LIST_JUST + "=" 2748 + URLEncoder.encode(ctx.session.user.email) + "&intlocs_change=b' >[Change this]</a>"); 2749 } 2750 ctx.println("</ul>"); 2751 2752 } // end intlocs 2753 ctx.println("<br>"); 2754 } 2755 if (justme || UserRegistry.userCanModifyUsers(ctx.session.user)) { 2756 ctx.println("<br>"); 2757 ctx.println("<input type='submit' name='doBtn' value='Do Action'>"); 2758 ctx.println("</form>"); 2759 2760 if (!justme && UserRegistry.userCanModifyUsers(ctx.session.user)) { 2761 WebContext subsubCtx = new WebContext(ctx); 2762 subsubCtx.addQuery("s", ctx.session.id); 2763 if (org != null) { 2764 subsubCtx.addQuery("org", org); 2765 } 2766 subsubCtx.addQuery("do", "list"); 2767 subsubCtx.println("<hr><form method='POST' action='" + subsubCtx.context("DataExport.jsp") + "'>"); 2768 subsubCtx.printUrlAsHiddenFields(); 2769 subsubCtx.print("<input type='submit' class='csvDownload' value='Download .csv (including LOCKED)'>"); 2770 subsubCtx.println("</form>"); 2771 } 2772 } 2773 } /* end synchronized(reg) */ 2774 } catch (SQLException se) { 2775 SurveyLog.logger.log(java.util.logging.Level.WARNING, 2776 "Query for org " + org + " failed: " + DBUtils.unchainSqlException(se), se); 2777 ctx.println("<i>Failure: " + DBUtils.unchainSqlException(se) + "</i><br>"); 2778 } finally { 2779 DBUtils.close(conn); 2780 } 2781 if (just != null) { 2782 ctx.println("<a href='" + ctx.url() + ctx.urlConnector() + "do=list'>\u22d6 Show all users</a><br>"); 2783 } 2784 printFooter(ctx); 2785 } 2786 2787 /** 2788 * @param ctx 2789 * @param userEmail 2790 * @param text 2791 * TODO 2792 */ printUserZoomLink(WebContext ctx, String userEmail, String text)2793 private void printUserZoomLink(WebContext ctx, String userEmail, String text) { 2794 ctx.print("<a href='" + ctx.url() + ctx.urlConnector() + "do=list&" + LIST_JUST + "=" + URLEncoder.encode(userEmail) + "' >" 2795 + ctx.iconHtml("zoom", "More on this user..") + text + "</a>"); 2796 } 2797 doChangeUserOption(WebContext ctx, int newLevel, int theirLevel, boolean selected)2798 private void doChangeUserOption(WebContext ctx, int newLevel, int theirLevel, boolean selected) { 2799 if (ctx.session.user.getLevel().canCreateOrSetLevelTo(VoteResolver.Level.fromSTLevel(newLevel))) { 2800 ctx.println(" <option " + /* (selected?" SELECTED ":"") + */"value='" + LIST_ACTION_SETLEVEL + newLevel 2801 + "'>Make " + UserRegistry.levelToStr(ctx, newLevel) + "</option>"); 2802 } else { 2803 ctx.println(" <option disabled " + ">Make " + UserRegistry.levelToStr(ctx, newLevel) + "</option>"); 2804 } 2805 } 2806 2807 /** 2808 * Show a toggleable preference 2809 * 2810 * @param ctx 2811 * @param pref 2812 * which preference 2813 * @param what 2814 * description of preference 2815 * 2816 * Called from debug_jsp.jspf as well as locally 2817 */ showTogglePref(WebContext ctx, String pref, String what)2818 public boolean showTogglePref(WebContext ctx, String pref, String what) { 2819 boolean val = ctx.prefBool(pref); 2820 WebContext nuCtx = (WebContext) ctx.clone(); 2821 nuCtx.addQuery(pref, !val); 2822 nuCtx.println("<a href='" + nuCtx.url() + "'>" + what + " is currently "); 2823 ctx.println(((val) ? "<span class='selected'>On</span>" : "<span style='color: #ddd' class='notselected'>On</span>") 2824 + " / " 2825 + ((!val) ? "<span class='selected'>Off</span>" : "<span style='color: #ddd' class='notselected'>Off</span>")); 2826 ctx.println("</a><br>"); 2827 return val; 2828 } 2829 showListPref(WebContext ctx, String pref, String what, String[] list, boolean doDef)2830 private String showListPref(WebContext ctx, String pref, String what, String[] list, boolean doDef) { 2831 String val = ctx.pref(pref, doDef ? "default" : list[0]); 2832 ctx.println("<b>" + what + "</b>: "); 2833 if (doDef) { 2834 WebContext nuCtx = (WebContext) ctx.clone(); 2835 nuCtx.addQuery(pref, "default"); 2836 ctx.println("<a href='" + nuCtx.url() + "' class='" + (val.equals("default") ? "selected" : "notselected") + "'>" 2837 + "default" + "</a> "); 2838 } 2839 for (int n = 0; n < list.length; n++) { 2840 WebContext nuCtx = (WebContext) ctx.clone(); 2841 nuCtx.addQuery(pref, list[n]); 2842 ctx.println("<a href='" + nuCtx.url() + "' class='" + (val.equals(list[n]) ? "selected" : "notselected") + "'>" 2843 + list[n] + "</a> "); 2844 } 2845 ctx.println("<br>"); 2846 return val; 2847 } 2848 getListSetting(WebContext ctx, String pref, String[] list, boolean doDef)2849 String getListSetting(WebContext ctx, String pref, String[] list, boolean doDef) { 2850 String defaultVal = doDef ? "default" : list[0]; 2851 String settingsSet = defaultVal; // do NOT persist!>> 2852 String val = ctx.pref(pref, settingsSet); 2853 return val; 2854 } 2855 getListSetting(UserSettings settings, String pref, String[] list, boolean doDef)2856 String getListSetting(UserSettings settings, String pref, String[] list, boolean doDef) { 2857 return settings.get(pref, doDef ? "default" : list[0]); 2858 } 2859 writeMenu(WebContext jout, String title, String field, String current, String items[], String rec)2860 private static void writeMenu(WebContext jout, String title, String field, String current, String items[], String rec) { 2861 String which = current; 2862 boolean any = false; 2863 for (int i = 0; !any && (i < items.length); i++) { 2864 if (items[i].equals(which)) 2865 any = true; 2866 } 2867 2868 String hash = "menu_" + field; 2869 String theTitle = ""; 2870 if (rec != null && !rec.isEmpty()) { 2871 theTitle = "(* denotes default value)"; 2872 } 2873 2874 jout.println("<label id='m_" + hash + "' class='" + (!current.equals(items[0]) ? "menutop-active" : "menutop-other") 2875 + "' title='" + theTitle + "' >"); 2876 jout.println(title); 2877 jout.println("<select class='" + (any ? "menutop-active" : "menutop-other") + "' onchange='window.location=this.value;'>"); 2878 2879 if (!any) { 2880 jout.println("<option selected value=\"\">Change...</option>"); 2881 } 2882 for (int i = 0; i < items.length; i++) { 2883 boolean isOptional = (items[i].equals(Level.COMPREHENSIVE.toString())); 2884 2885 if (isOptional && !SurveyMain.isUnofficial()) 2886 continue; 2887 WebContext ssc = new WebContext(jout); 2888 ssc.setQuery(field, items[i]); 2889 String sty = ""; 2890 if (rec != null && rec.equals(items[i])) { 2891 sty = "font-weight: bold;"; 2892 } 2893 2894 jout.print("<option style='" + sty + "' "); 2895 if (items[i].equals(which)) { 2896 jout.print(" selected "); 2897 } else { 2898 jout.print("value=\"" + ssc.url() + "\" "); 2899 } 2900 jout.print(">" + items[i]); 2901 if (rec != null && rec.equals(items[i])) { 2902 jout.print("*"); 2903 } 2904 if (isOptional) { 2905 jout.println(" [only available in SmokeTest]"); 2906 } 2907 jout.println("</option>"); 2908 } 2909 jout.println("</select>"); 2910 2911 jout.println("<span id='info_" + hash + "'/>"); 2912 2913 jout.println("</label>"); 2914 } 2915 showListSetting(WebContext ctx, String pref, String what, String[] list)2916 String showListSetting(WebContext ctx, String pref, String what, String[] list) { 2917 return showListSetting(ctx, pref, what, list, false); 2918 } 2919 showListSetting(WebContext ctx, String pref, String what, String[] list, boolean doDef)2920 String showListSetting(WebContext ctx, String pref, String what, String[] list, boolean doDef) { 2921 return showListSetting(ctx, pref, what, list, doDef, null); 2922 } 2923 showListSetting(WebContext ctx, String pref, String what, String[] list, String rec)2924 String showListSetting(WebContext ctx, String pref, String what, String[] list, String rec) { 2925 return showListSetting(ctx, pref, what, list, false, rec); 2926 } 2927 showListSetting(WebContext ctx, String pref, String what, String[] list, boolean doDef, String rec)2928 String showListSetting(WebContext ctx, String pref, String what, String[] list, boolean doDef, String rec) { 2929 String val = getListSetting(ctx, pref, list, doDef); 2930 ctx.settings().set(pref, val); 2931 2932 boolean no_js = ctx.prefBool(SurveyMain.PREF_NOJAVASCRIPT); 2933 2934 if (no_js) { 2935 ctx.println("<b>" + what + "</b>: "); 2936 if (doDef) { 2937 WebContext nuCtx = (WebContext) ctx.clone(); 2938 nuCtx.addQuery(pref, "default"); 2939 ctx.println("<a href='" + nuCtx.url() + "' class='" + (val.equals("default") ? "selected" : "notselected") + "'>" 2940 + "default" + "</a> "); 2941 } 2942 for (int n = 0; n < list.length; n++) { 2943 WebContext nuCtx = (WebContext) ctx.clone(); 2944 nuCtx.addQuery(pref, list[n]); 2945 if (rec != null && rec.equals(list[n])) { 2946 ctx.print("<b>"); 2947 } 2948 ctx.println("<a href='" + nuCtx.url() + "' class='" + (val.equals(list[n]) ? "selected" : "notselected") + "'>" 2949 + list[n] + "</a> "); 2950 if (rec != null && rec.equals(list[n])) { 2951 ctx.print("*</b>"); 2952 } 2953 } 2954 ctx.println("<br>"); 2955 } else { 2956 writeMenu(ctx, what, pref, val, list, rec); 2957 } 2958 2959 return val; 2960 } 2961 doOptions(WebContext ctx)2962 private void doOptions(WebContext ctx) { 2963 WebContext subCtx = new WebContext(ctx); 2964 subCtx.removeQuery(QUERY_DO); 2965 printHeader(ctx, "Manage"); 2966 printUserTableWithHelp(ctx, "/MyOptions"); 2967 2968 ctx.println("<a href='" + ctx.url() + "'>Locales</a><hr>"); 2969 printRecentLocales(subCtx, ctx); 2970 ctx.addQuery(QUERY_DO, "options"); 2971 ctx.println("<h2>Manage</h2>"); 2972 2973 ctx.includeFragment("manage.jsp"); 2974 printFooter(ctx); 2975 } 2976 2977 /** 2978 * Do session. 2979 * 2980 * @param ctx 2981 * @throws IOException 2982 * @throws SurveyException 2983 * 2984 * Called only by doGet. Called when user logs in or logs out, also when choose Settings from gear menu. 2985 */ doSession(WebContext ctx)2986 private void doSession(WebContext ctx) throws IOException, SurveyException { 2987 // which 2988 String which = ctx.field(QUERY_SECTION); // may be empty string "" 2989 2990 setLocale(ctx); 2991 2992 String sessionMessage = ctx.setSession(); 2993 2994 if (ctx.session == null) { 2995 2996 printHeader(ctx, "Survey Tool"); 2997 if (sessionMessage == null) { 2998 sessionMessage = "Could not create your user session."; 2999 } 3000 ctx.println("<p><img src='stop.png' width='16'>" + sessionMessage + "</p>"); 3001 ctx.println("<hr><a href='" + ctx.context("login.jsp") + "' class='notselected'>Login as another user...</a>"); 3002 printFooter(ctx); 3003 return; 3004 } else { 3005 ctx.session.userDidAction(); // always true for this 3006 } 3007 3008 if (lockOut != null) { 3009 if (ctx.field("unlock").equals(lockOut)) { 3010 ctx.session.put("unlock", lockOut); 3011 } else { 3012 String unlock = (String) ctx.session.get("unlock"); 3013 if ((unlock == null) || (!unlock.equals(lockOut))) { 3014 printHeader(ctx, "Locked for Maintenance"); 3015 ctx.print("<hr><div class='ferrbox'>Sorry, the Survey Tool has been locked for maintenance work. Please try back later.</div>"); 3016 printFooter(ctx); 3017 return; 3018 } 3019 } 3020 } 3021 3022 // setup thread name 3023 if (ctx.session.user != null) { 3024 Thread.currentThread().setName( 3025 Thread.currentThread().getName() + " " + ctx.session.user.id + ":" + ctx.session.user.toString()); 3026 3027 } 3028 3029 // locale REDIRECTS ------------------------------ 3030 // looking for a stringid? 3031 String strid = ctx.field("strid"); 3032 String whyBad = "(unknown problem)"; 3033 if (!strid.isEmpty() && ctx.hasField("_")) { 3034 try { 3035 final String xpath = xpt.getByStringID(strid); 3036 if (xpath != null) { 3037 // got one. 3038 PathHeader ph = getSTFactory().getPathHeader(xpath); 3039 if (ph == null) { 3040 whyBad = "NULL from PathHeader"; 3041 } else if (ph.getSurveyToolStatus() == SurveyToolStatus.HIDE 3042 || ph.getSurveyToolStatus() == SurveyToolStatus.DEPRECATED) { 3043 whyBad = "This item's PathHeader status is: " + ph.getSurveyToolStatus().name(); 3044 } else { 3045 ctx.response.sendRedirect(ctx.vurl(CLDRLocale.getInstance(ctx.field("_")), ph.getPageId(), strid, null)); 3046 return; // exit 3047 // } 3048 } 3049 } else { 3050 whyBad = "not a valid StringID"; 3051 } 3052 SurveyLog.logException(null, "Bad StringID" + strid + " " + whyBad, ctx); 3053 } catch (Throwable t) { 3054 SurveyLog.logException(t, "Exception processing StringID " + strid + " - " + whyBad, ctx); 3055 } 3056 } 3057 3058 // END REDIRECTS ------------------------- 3059 3060 // TODO: untangle this 3061 // admin things 3062 if ((ctx.field(QUERY_DO).length() > 0)) { 3063 String doWhat = ctx.field(QUERY_DO); 3064 3065 // could be user or non-user items 3066 if (doWhat.equals("options")) { 3067 doOptions(ctx); 3068 return; 3069 } else if (doWhat.equals("disputed")) { 3070 DisputePageManager.doDisputed(ctx); 3071 return; 3072 } else if (doWhat.equals("logout")) { 3073 ctx.logout(); 3074 try { 3075 ctx.response.sendRedirect(ctx.jspLink("?logout=1")); 3076 ctx.out.close(); 3077 ctx.close(); 3078 } catch (IOException ioe) { 3079 throw new RuntimeException(ioe.toString() + " while redirecting to logout"); 3080 } 3081 return; 3082 } 3083 3084 // these items are only for users. 3085 if (ctx.session.user != null) { 3086 if ((doWhat.equals("list") || doWhat.equals("listu")) && (UserRegistry.userCanDoList(ctx.session.user))) { 3087 doList(ctx); 3088 return; 3089 } else if (doWhat.equals("coverage") && (UserRegistry.userCanDoList(ctx.session.user))) { 3090 doCoverage(ctx); 3091 return; 3092 } else if (doWhat.equals("new") && (UserRegistry.userCanCreateUsers(ctx.session.user))) { 3093 doNew(ctx); 3094 return; 3095 } 3096 } 3097 // Option wasn't found 3098 sessionMessage = ("<i id='sessionMessage'>Could not do the action '" + doWhat + "'. You may need to be logged in first.</i>"); 3099 } 3100 3101 String title = " "; 3102 PageId pageId = ctx.getPageId(); 3103 if (pageId != null) { 3104 title = ""; 3105 } else if (ctx.hasField(QUERY_EXAMPLE)) { 3106 title = title + " Example"; 3107 } else if (which == null || which.isEmpty()) { 3108 if (ctx.getLocale() == null) { 3109 ctx.redirect(ctx.context(VURL_LOCALES)); 3110 ctx.redirectToVurl(ctx.context(VURL_LOCALES)); // may blink. 3111 return; 3112 } else { 3113 title = ""; // general"; 3114 } 3115 } 3116 /* 3117 * TODO: all of this function from here on might be dead code; if dead, delete 3118 */ 3119 printHeader(ctx, title); 3120 if (sessionMessage != null) { 3121 ctx.println(sessionMessage); 3122 } 3123 3124 WebContext baseContext = (WebContext) ctx.clone(); 3125 3126 // Don't spin up a factory here. 3127 3128 // print 'shopping cart' 3129 if (!shortHeader(ctx)) { 3130 3131 if ((which.length() == 0) && (ctx.getLocale() != null) || (pageId == null && !which.startsWith(REPORT_PREFIX))) { 3132 /* 3133 * unrecognized page id 3134 */ 3135 which = xMAIN; 3136 } 3137 printUserTable(ctx); 3138 printRecentLocales(baseContext, ctx); 3139 } 3140 3141 /* 3142 * Don't show these warnings for example pages. 3143 */ 3144 if ((ctx.getLocale() != null) && (!shortHeader(ctx))) { 3145 CLDRLocale aliasTarget = isLocaleAliased(ctx.getLocale()); 3146 if (aliasTarget != null) { 3147 /* 3148 * The alias might be a default content locale. Save some clicks here. 3149 */ 3150 CLDRLocale dcParent = getSupplementalDataInfo().getBaseFromDefaultContent(aliasTarget); 3151 if (dcParent == null) { 3152 dcParent = aliasTarget; 3153 } 3154 ctx.println("<div class='ferrbox'>This locale is aliased to <b>" + getLocaleLink(ctx, aliasTarget, null) 3155 + "</b>. You cannot modify it. Please make all changes in <b>" + getLocaleLink(ctx, dcParent, null) 3156 + "</b>.<br>"); 3157 ctx.printHelpLink("/AliasedLocale", "Help with Aliased Locale"); 3158 ctx.print("</div>"); 3159 3160 ctx.println("<div class='ferrbox'><h1>" 3161 + ctx.iconHtml("stop", null) 3162 + "We apologise for the inconvenience, but there is currently an error with how these aliased locales are resolved. Kindly ignore this locale for the time being. You must make all changes in <b>" 3163 + getLocaleLink(ctx, dcParent, null) + "</b>.</h1>"); 3164 ctx.print("</div>"); 3165 3166 } 3167 } 3168 doLocale(ctx, baseContext, which, whyBad); 3169 } 3170 printRecentLocales(WebContext baseContext, WebContext ctx)3171 private void printRecentLocales(WebContext baseContext, WebContext ctx) { 3172 Hashtable<String, Hashtable<String, Object>> lh = ctx.session.getLocales(); 3173 Enumeration<String> e = lh.keys(); 3174 if (e.hasMoreElements()) { 3175 boolean shownHeader = false; 3176 for (; e.hasMoreElements();) { 3177 String k = e.nextElement().toString(); 3178 if ((ctx.getLocale() != null) && (ctx.getLocale().toString().equals(k))) { 3179 continue; 3180 } 3181 if (!shownHeader) { 3182 ctx.println("<p align='right'><B>Recent locales: </B> "); 3183 shownHeader = true; 3184 } 3185 ctx.print(getLocaleLink(ctx, k, null)); 3186 } 3187 if (shownHeader) { 3188 ctx.println("</p>"); 3189 } 3190 } 3191 } 3192 shortHeader(WebContext ctx)3193 private static boolean shortHeader(WebContext ctx) { 3194 return ctx.hasField(QUERY_EXAMPLE); 3195 } 3196 3197 private LocaleTree localeTree = null; 3198 getLocaleTree()3199 public synchronized LocaleTree getLocaleTree() { 3200 if (localeTree == null) { 3201 CLDRFormatter defaultFormatter = setDefaultCLDRLocaleFormatter(); 3202 LocaleTree newLocaleTree = new LocaleTree(defaultFormatter); 3203 File inFiles[] = getInFiles(); 3204 if (inFiles == null) { 3205 busted("Can't load CLDR data files from " + fileBase); 3206 throw new RuntimeException("Can't load CLDR data files from " + fileBase); 3207 } 3208 int nrInFiles = inFiles.length; 3209 3210 for (int i = 0; i < nrInFiles; i++) { 3211 String localeName = inFiles[i].getName(); 3212 int dot = localeName.indexOf('.'); 3213 if (dot != -1) { 3214 localeName = localeName.substring(0, dot); 3215 CLDRLocale loc = CLDRLocale.getInstance(localeName); 3216 3217 // but, is it just an alias? 3218 CLDRLocale aliasTo = isLocaleAliased(loc); 3219 if (aliasTo == null) { 3220 newLocaleTree.add(loc); 3221 } 3222 } 3223 } 3224 localeTree = newLocaleTree; 3225 } 3226 return localeTree; 3227 } 3228 3229 /** 3230 * @return 3231 */ setDefaultCLDRLocaleFormatter()3232 private CLDRFormatter setDefaultCLDRLocaleFormatter() { 3233 CLDRFormatter defaultFormatter = new CLDRLocale.CLDRFormatter(getEnglishFile(), FormatBehavior.replace); 3234 CLDRLocale.setDefaultFormatter(defaultFormatter); 3235 return defaultFormatter; 3236 } 3237 3238 /** 3239 * Get all related locales, given a 'top' (highestNonrootParent) locale. Example: ar -> ar, ar_EG ... skips readonly locales. 3240 * @see CLDRLocale#getHighestNonrootParent() 3241 * @param topLocale 3242 * @return the resulting set, unmodifiable 3243 */ getRelatedLocs(CLDRLocale topLocale)3244 public synchronized Collection<CLDRLocale> getRelatedLocs(CLDRLocale topLocale) { 3245 Set<CLDRLocale> cachedSet = relatedLocales.get(topLocale); 3246 if (cachedSet == null) { 3247 final LocaleTree lt = getLocaleTree(); 3248 final Set<CLDRLocale> set = new HashSet<>(); 3249 set.add(topLocale); // add the top locale itself 3250 for (CLDRLocale atopLocale : lt.getTopCLDRLocales()) { // add each of the top locales that has the same "highest nonroot parent" 3251 if (atopLocale.getHighestNonrootParent() == topLocale) { 3252 final Collection<CLDRLocale> topLocales = lt.getSubLocales(atopLocale).values(); 3253 if (topLocales != null) { 3254 set.addAll(topLocales); 3255 } 3256 } 3257 } 3258 cachedSet = Collections.unmodifiableSet(set); 3259 relatedLocales.put(topLocale, cachedSet); 3260 } 3261 return cachedSet; 3262 } 3263 3264 private Map<CLDRLocale, Set<CLDRLocale>> relatedLocales = new HashMap<>(); 3265 3266 /** 3267 * 3268 * @param localeName 3269 * @param str 3270 * @param explanation 3271 * @return 3272 * 3273 * Called from st_top.jsp and locally 3274 */ decoratedLocaleName(CLDRLocale localeName, String str, String explanation)3275 public static String decoratedLocaleName(CLDRLocale localeName, String str, String explanation) { 3276 String rv = ""; 3277 if (explanation.length() > 0) { 3278 rv = rv + ("<span title='" + explanation + "'>"); 3279 } 3280 rv = rv + (str); 3281 if (explanation.length() > 0) { 3282 rv = rv + ("</span>"); 3283 } 3284 return rv; 3285 } 3286 getLocaleLink(WebContext ctx, String locale, String n)3287 private String getLocaleLink(WebContext ctx, String locale, String n) { 3288 return getLocaleLink(ctx, CLDRLocale.getInstance(locale), n); 3289 } 3290 3291 /** 3292 * 3293 * @param ctx 3294 * @param locale 3295 * @param n 3296 * @return 3297 * 3298 * Called from generalinfo.jsp and locally 3299 */ getLocaleLink(WebContext ctx, CLDRLocale locale, String n)3300 public String getLocaleLink(WebContext ctx, CLDRLocale locale, String n) { 3301 if (n == null) { 3302 n = locale.getDisplayName(); 3303 } 3304 boolean isDefaultContent = getSupplementalDataInfo().isDefaultContent(locale); 3305 String title = locale.toString(); 3306 String classstr = ""; 3307 String localeUrl = ctx.urlForLocale(locale); 3308 if (isDefaultContent) { 3309 classstr = "class='dcLocale'"; 3310 localeUrl = null; // ctx.urlForLocale(defaultContentToParent(locale)); 3311 title = "Default Content: Please view and/or propose changes in " 3312 + getSupplementalDataInfo().getBaseFromDefaultContent(locale).getDisplayName() + "."; 3313 } 3314 String rv = ("<a " + classstr + " title='" + title + "' " + (localeUrl != null ? ("href=\"" + localeUrl + "\"") : "") + " >"); 3315 rv = rv + decoratedLocaleName(locale, n, title); 3316 boolean canModify = !isDefaultContent && UserRegistry.userCanModifyLocale(ctx.session.user, locale); 3317 if (canModify) { 3318 rv = rv + (modifyThing(ctx)); 3319 int odisp = 0; 3320 if ((SurveyMain.phase() == Phase.VETTING || SurveyMain.phase() == Phase.SUBMIT || isPhaseVettingClosed()) 3321 && ((odisp = DisputePageManager.getOrgDisputeCount(ctx)) > 0)) { 3322 rv = rv + ctx.iconHtml("disp", "(" + odisp + " org disputes)"); 3323 } 3324 } 3325 if (!isDefaultContent && getReadOnlyLocales().contains(locale)) { 3326 String comment = SpecialLocales.getComment(locale); 3327 if (comment == null) { 3328 comment = "This locale is read-only due to SurveyTool configuration."; 3329 } 3330 rv = rv + ctx.iconHtml("lock", comment); 3331 } 3332 rv = rv + ("</a>"); 3333 // ctx.print(hasDraft?"</b>":"") ; 3334 3335 return rv; 3336 } 3337 3338 /** 3339 * 3340 * @param ctx 3341 * @param baseContext 3342 * @param which 3343 * @param whyBad 3344 * 3345 * Called by doSession -- but possibly never-reached dead code? 3346 */ doLocale(WebContext ctx, WebContext baseContext, String which, String whyBad)3347 private void doLocale(WebContext ctx, WebContext baseContext, String which, String whyBad) { 3348 String locale = null; 3349 if (ctx.getLocale() != null) { 3350 locale = ctx.getLocale().toString(); 3351 } 3352 if ((locale == null) || (locale.length() <= 0)) { 3353 ctx.println("<i>Loading locale list...</i>"); 3354 ctx.flush(); 3355 ctx.redirectToVurl(ctx.context(VURL_LOCALES)); // may blink. 3356 return; 3357 } else { 3358 showLocale(ctx, which, whyBad); 3359 } 3360 printFooter(ctx); 3361 } 3362 3363 /** 3364 * Print out a menu item 3365 * 3366 * @param ctx 3367 * the context 3368 * @param which 3369 * the ID of "this" item 3370 * @param menu 3371 * the ID of the current item 3372 * @param title 3373 * the Title of this menu 3374 * @param key 3375 * the URL field to use (such as 'x') 3376 * 3377 * Called by doList and WebContext.showCoverageLevel, and from jsp 3378 */ printMenu(WebContext ctx, String which, String menu, String title, String key)3379 public static void printMenu(WebContext ctx, String which, String menu, String title, String key) { 3380 ctx.print(getMenu(ctx, which, menu, title, key)); 3381 } 3382 3383 /** 3384 * 3385 * @param ctx 3386 * @param which 3387 * @param menu 3388 * @param title 3389 * @param key 3390 * @return 3391 * 3392 * Called by printMenu above; and from menu.tag 3393 */ getMenu(WebContext ctx, String which, String menu, String title, String key)3394 public static String getMenu(WebContext ctx, String which, String menu, String title, String key) { 3395 StringBuffer buf = new StringBuffer(); 3396 if (menu.equals(which)) { 3397 buf.append("<b class='selected'>"); 3398 } else { 3399 buf.append("<a class='notselected' href=\"" + ctx.url() + ctx.urlConnector() + key + "=" + menu 3400 + "\">"); 3401 } 3402 if (menu.endsWith("/")) { 3403 buf.append(title + "<font size=-1>(other)</font>"); 3404 } else { 3405 buf.append(title); 3406 } 3407 if (menu.equals(which)) { 3408 buf.append("</b>"); 3409 } else { 3410 buf.append("</a>"); 3411 } 3412 return buf.toString(); 3413 } 3414 notifyUser(WebContext ctx, String theirEmail, String pass)3415 void notifyUser(WebContext ctx, String theirEmail, String pass) { 3416 UserRegistry.User u = reg.get(theirEmail); 3417 String whySent; 3418 String subject = "CLDR Registration for " + theirEmail; 3419 Integer fromId; 3420 if (ctx != null) { 3421 fromId = ctx.userId(); 3422 whySent = "You are being notified of the CLDR vetting account for you.\n"; 3423 } else { 3424 fromId = null; 3425 whySent = "Your CLDR vetting account information is being sent to you\r\n\r\n"; 3426 } 3427 String body = whySent + "To access it, visit: \n<" 3428 + defaultBase + "?" + QUERY_PASSWORD + "=" + pass + "&" 3429 + QUERY_EMAIL + "=" + theirEmail 3430 + ">\n" 3431 + 3432 // // DO NOT ESCAPE THIS AMPERSAND. 3433 "\n" + "Or you can visit\n <" + defaultBase + ">\n username: " + theirEmail 3434 + "\n password: " + pass + "\n" + "\n" + " Please keep this link to yourself. Thanks.\n" 3435 + " Follow the 'Instructions' link on the main page for more help.\n" + 3436 "As a reminder, please do not re-use this password on other web sites.\n\n"; 3437 MailSender.getInstance().queue(fromId, u.id, subject, body); 3438 } 3439 3440 public static final String CHECKCLDR = "CheckCLDR_"; // key for CheckCLDR objects by locale 3441 public static final String CHECKCLDR_RES = "CheckCLDR_RES_"; // key for CheckCLDR objects by locale 3442 3443 /** 3444 * 3445 * @param ctx 3446 * @param which 3447 * 3448 * TODO: is this dead/unreachable? Called only by showLocale 3449 */ printLocaleTreeMenu(WebContext ctx, String which)3450 private void printLocaleTreeMenu(WebContext ctx, String which) { 3451 3452 WebContext subCtx = (WebContext) ctx.clone(); 3453 subCtx.addQuery(QUERY_LOCALE, ctx.getLocale().toString()); 3454 3455 ctx.println("<div id='sectionmenu'>"); 3456 3457 boolean canModify = UserRegistry.userCanModifyLocale(subCtx.session.user, subCtx.getLocale()); 3458 subCtx.put("which", which); 3459 subCtx.put(WebContext.CAN_MODIFY, canModify); 3460 subCtx.includeFragment("menu_top.jsp"); // ' code lists .. ' etc 3461 subCtx.println("</div>"); 3462 } 3463 3464 /** 3465 * show the actual locale data.. 3466 * 3467 * @param ctx 3468 * context 3469 * @param which 3470 * value of 'x' parameter. 3471 * 3472 * Called by doLocale -- but possibly never-reached dead code? 3473 */ showLocale(WebContext ctx, String which, String whyBad)3474 private void showLocale(WebContext ctx, String which, String whyBad) { 3475 PageId pageId = ctx.getPageId(); 3476 synchronized (ctx.session) { 3477 // Set up checks 3478 if (ctx.hasField(QUERY_EXAMPLE)) { 3479 ctx.println("<h3>" + ctx.getLocale() + " " + ctx.getLocale().getDisplayName() + " / " + which + " Example</h3>"); 3480 } else { 3481 // does not need check 3482 printLocaleTreeMenu(ctx, which); 3483 } 3484 3485 // check for errors 3486 ctx.includeFragment("possibleProblems.jsp"); 3487 3488 // Find which pod they want, and show it. 3489 // NB keep these sections in sync with DataPod.xpathToPodBase() 3490 WebContext subCtx = (WebContext) ctx.clone(); 3491 subCtx.addQuery(QUERY_LOCALE, ctx.getLocale().toString()); 3492 subCtx.addQuery(QUERY_SECTION, which); 3493 // looking for a stringid? Should have redirected by now. 3494 if (ctx.hasField("strid")) { 3495 String xpath = "(unknown StringID)"; 3496 String strid = ctx.field("strid"); 3497 try { 3498 xpath = xpt.getByStringID(strid); 3499 if (xpath == null) { 3500 xpath = "(not a valid StringID)"; 3501 } 3502 } catch (Throwable t) { 3503 // SurveyLog.logException(t, ctx); 3504 } 3505 ctx.println("<div class='ferrbox'> " + ctx.iconHtml("stop", "bad xpath") 3506 + " Sorry, the string ID in your URL can't be shown: <span class='loser' title='" + xpath + " " + whyBad + "'>" + strid 3507 + "</span><br>The XPath involved is: <tt>" + xpath + "</tt><br> and the reason is: " + whyBad + ".</div>"); 3508 which = xMAIN; 3509 return; 3510 } 3511 3512 if (pageId != null && !which.equals(xMAIN)) { 3513 showPathList(subCtx, which, pageId); 3514 } else { 3515 which = xMAIN; 3516 doMain(subCtx); // TODO: does this ever happen? Or is doMain effectively dead code? 3517 } 3518 } 3519 } 3520 3521 /** 3522 * @param localeName 3523 * @return 3524 */ fileNameToLocale(String localeName)3525 private CLDRLocale fileNameToLocale(String localeName) { 3526 String theLocale; 3527 int dot = localeName.indexOf('.'); 3528 theLocale = localeName.substring(0, dot); 3529 return CLDRLocale.getInstance(theLocale); 3530 } 3531 3532 /** 3533 * Show the 'main info about this locale' (General) panel. 3534 */ doMain(WebContext ctx)3535 private void doMain(WebContext ctx) { 3536 ctx.includeFragment("generalinfo.jsp"); 3537 } 3538 3539 private static CLDRFile gTranslationHintsFile = null; 3540 private static ExampleGenerator gTranslationHintsExample = null; 3541 3542 private Factory gFactory = null; 3543 3544 /** 3545 * Return the factory that corresponds to trunk 3546 * 3547 * @return 3548 */ getDiskFactory()3549 public synchronized Factory getDiskFactory() { 3550 if (gFactory == null) { 3551 final File list[] = getFileBases(); 3552 CLDRConfig config = CLDRConfig.getInstance(); 3553 // may fail at server startup time- should do this through setup mode 3554 ensureOrCheckout(null, "CLDR_DIR", config.getCldrBaseDirectory(), CLDR_DIR_REPOS); 3555 // verify readable 3556 File root = new File(config.getCldrBaseDirectory(), "common/main"); 3557 if (!root.isDirectory()) { 3558 throw new InternalError("Not a dir: " + root.getAbsolutePath() + " - check the value of " + "CLDR_DIR" 3559 + " in cldr.properties."); 3560 } 3561 3562 gFactory = SimpleFactory.make(list, ".*"); 3563 } 3564 return gFactory; 3565 } 3566 ensureOrCheckout(JspWriter o, final String param, final File dir, final String url)3567 private void ensureOrCheckout(JspWriter o, final String param, final File dir, final String url) { 3568 if (dir == null) { 3569 busted("Configuration Error: " + param + " is not set."); 3570 } else if (!dir.isDirectory()) { 3571 if (o == null) { 3572 busted("Not able to checkout " + dir.getAbsolutePath() + " for " + param + " - go into setup mode."); 3573 return; /* NOTREACHED */ 3574 } 3575 throw new InternalError("Please checkout " + url + " " + dir.getAbsolutePath() 3576 + "' - and restart the server. TODO- this will be fixed by the step-by-step install."); 3577 } 3578 } 3579 3580 private STFactory gSTFactory = null; 3581 3582 /** 3583 * Get the factory corresponding to the current snapshot. 3584 * 3585 * @return 3586 */ getSTFactory()3587 public final synchronized STFactory getSTFactory() { 3588 if (gSTFactory == null) { 3589 gSTFactory = new STFactory(this); 3590 } 3591 return gSTFactory; 3592 } 3593 3594 /** 3595 * destroy the ST Factory - testing use only! 3596 * 3597 * @internal 3598 */ TESTING_removeSTFactory()3599 public final synchronized STFactory TESTING_removeSTFactory() { 3600 STFactory oldFactory = gSTFactory; 3601 gSTFactory = null; 3602 return oldFactory; 3603 } 3604 3605 /** 3606 * This is the TRANSLATION HINTS FILE (en_ZZ) - thus it contains 'translation hints'. 3607 * @see {@link #TRANS_HINT_ID} 3608 * @see {@link #getEnglishFile()} 3609 * @return 3610 */ getTranslationHintsFile()3611 public synchronized CLDRFile getTranslationHintsFile() { 3612 if (gTranslationHintsFile == null) { 3613 try { 3614 CLDRFile file = getDiskFactory().make(TRANS_HINT_LOCALE.toString(), true); 3615 file.setSupplementalDirectory(getSupplementalDirectory()); // so the icuServiceBuilder doesn't blow up. 3616 file.freeze(); // so it can be shared. 3617 gTranslationHintsFile = file; 3618 3619 // propagate it. 3620 CheckCLDR.setDisplayInformation(gTranslationHintsFile); 3621 setDefaultCLDRLocaleFormatter(); 3622 } catch (Throwable t) { 3623 busted("Could not load translation hints locale " + TRANS_HINT_LOCALE, t); 3624 } 3625 } 3626 return gTranslationHintsFile; 3627 } 3628 3629 private Set<UserLocaleStuff> allUserLocaleStuffs = new HashSet<>(); 3630 3631 public static final String QUERY_VALUE_SUFFIX = "_v"; 3632 3633 /** 3634 * 3635 * @return 3636 * 3637 * Called by DataSection.DataRow.toJSONString, and from helpHtml.jsp, and locally by doStartup 3638 */ getTranslationHintsExample()3639 public synchronized ExampleGenerator getTranslationHintsExample() { 3640 if (gTranslationHintsExample == null) { 3641 CLDRFile translationHintsFile = getTranslationHintsFile(); 3642 gTranslationHintsExample = new ExampleGenerator(translationHintsFile, translationHintsFile, fileBase + "/../supplemental/"); 3643 } 3644 /* 3645 * TODO: to improve performance, move the following line inside the above "if" block, or explain why that can't be done. 3646 * Why would we need to check this more than once? Can the return value of twidBool change during a run of Survey Tool? 3647 */ 3648 gTranslationHintsExample.setVerboseErrors(twidBool("ExampleGenerator.setVerboseErrors")); 3649 return gTranslationHintsExample; 3650 } 3651 getHTMLDirectionFor(CLDRLocale locale)3652 public synchronized WebContext.HTMLDirection getHTMLDirectionFor(CLDRLocale locale) { 3653 String dir = getDirectionalityFor(locale); 3654 return HTMLDirection.fromCldr(dir); 3655 } 3656 getDirectionalityFor(CLDRLocale id)3657 private synchronized String getDirectionalityFor(CLDRLocale id) { 3658 final boolean DDEBUG = false; 3659 if (DDEBUG) 3660 SurveyLog.logger.warning("Checking directionality for " + id); 3661 if (aliasMap == null) { 3662 checkAllLocales(); 3663 } 3664 while (id != null) { 3665 // TODO use iterator 3666 CLDRLocale aliasTo = isLocaleAliased(id); 3667 if (DDEBUG) 3668 SurveyLog.logger.warning("Alias -> " + aliasTo); 3669 if (aliasTo != null && !aliasTo.equals(id)) { // prevent loops 3670 id = aliasTo; 3671 if (DDEBUG) 3672 SurveyLog.logger.warning(" -> " + id); 3673 continue; 3674 } 3675 String dir = directionMap.get(id); 3676 if (DDEBUG) 3677 SurveyLog.logger.warning(" dir:" + dir); 3678 if (dir != null) { 3679 return dir; 3680 } 3681 id = id.getParent(); 3682 if (DDEBUG) 3683 SurveyLog.logger.warning(" .. -> :" + id); 3684 } 3685 if (DDEBUG) 3686 SurveyLog.logger.warning("err: could not get directionality of root"); 3687 return "left-to-right"; // fallback 3688 } 3689 3690 /** 3691 * Returns the current basic options map. 3692 * 3693 * @return the map 3694 * @see org.unicode.cldr.test.CheckCoverage#check(String, String, String, 3695 * Map, List) 3696 */ getTestPhase()3697 public static final org.unicode.cldr.test.CheckCLDR.Phase getTestPhase() { 3698 return phase().getCPhase(); 3699 } 3700 createCheck()3701 public CheckCLDR createCheck() { 3702 CheckCLDR checkCldr; 3703 checkCldr = CheckCLDR.getCheckAll(getSTFactory(), "(?!.*(CheckCoverage).*).*"); 3704 3705 CheckCLDR.setDisplayInformation(getTranslationHintsFile()); 3706 3707 return checkCldr; 3708 } 3709 3710 /** 3711 * Any user of this should be within session sync. 3712 * 3713 * @author srl 3714 * 3715 */ 3716 public class UserLocaleStuff { 3717 public CLDRFile cldrfile = null; 3718 public XMLSource dbSource = null; 3719 public XMLSource resolvedSource = null; 3720 public Hashtable<String, Object> hash = new Hashtable<>(); 3721 private int use; 3722 CLDRFile resolvedFile = null; 3723 CLDRFile translationHintsFile; 3724 open()3725 public void open() { 3726 use++; 3727 if (SurveyLog.isDebug()) 3728 SurveyLog.logger.warning("uls: open=" + use); 3729 } 3730 3731 private String closeStack = null; 3732 close()3733 public void close() { 3734 final boolean DEBUG = CldrUtility.getProperty("TEST", false); 3735 if (use <= 0) { 3736 throw new InternalError("Already closed! use=" + use + ", closeStack:" + closeStack); 3737 } 3738 use--; 3739 closeStack = DEBUG ? StackTracker.currentStack() : null; 3740 if (SurveyLog.isDebug()) 3741 SurveyLog.logger.warning("uls: close=" + use); 3742 if (use > 0) { 3743 return; 3744 } 3745 internalClose(); 3746 synchronized (allUserLocaleStuffs) { 3747 allUserLocaleStuffs.remove(this); 3748 } 3749 } 3750 internalClose()3751 public void internalClose() { 3752 this.dbSource = null; 3753 } 3754 isClosed()3755 public boolean isClosed() { 3756 return this.dbSource == null; 3757 } 3758 UserLocaleStuff(CLDRLocale locale)3759 public UserLocaleStuff(CLDRLocale locale) { 3760 synchronized (allUserLocaleStuffs) { 3761 allUserLocaleStuffs.add(this); 3762 } 3763 3764 // TODO: refactor. 3765 if (cldrfile == null) { 3766 resolvedSource = getSTFactory().makeSource(locale.getBaseName(), true); 3767 dbSource = resolvedSource.getUnresolving(); 3768 cldrfile = getSTFactory().make(locale, true).setSupplementalDirectory(getSupplementalDirectory()); 3769 resolvedFile = cldrfile; 3770 translationHintsFile = getTranslationHintsFile(); 3771 } 3772 } 3773 clear()3774 public void clear() { 3775 hash.clear(); 3776 // TODO: try just kicking these instead of clearing? 3777 cldrfile = null; 3778 dbSource = null; 3779 hash.clear(); 3780 } 3781 } 3782 3783 /** 3784 * Return the UserLocaleStuff for the current context. Any user of this 3785 * should be within session sync (ctx.session) and must be balanced with 3786 * calls to close(); 3787 * 3788 * @param ctx 3789 * @param user 3790 * @param locale 3791 * @see UserLocaleStuff#close() 3792 * @see WebContext#getUserFile() 3793 */ getUserFile(CookieSession session, CLDRLocale locale)3794 public UserLocaleStuff getUserFile(CookieSession session, CLDRLocale locale) { 3795 UserLocaleStuff uf = null; 3796 uf = new UserLocaleStuff(locale); // always open a new 3797 uf.open(); // incr count. 3798 3799 return uf; 3800 } 3801 3802 private static Hashtable<CLDRLocale, CLDRLocale> aliasMap = null; 3803 private static Hashtable<CLDRLocale, String> directionMap = null; 3804 3805 /** 3806 * "Hash" a file to a string, including mod time and size 3807 * 3808 * @param f 3809 * @return 3810 */ fileHash(File f)3811 private static String fileHash(File f) { 3812 return ("[" + f.getAbsolutePath() + "|" + f.length() + "|" + f.hashCode() + "|" + f.lastModified() + "]"); 3813 } 3814 checkAllLocales()3815 private synchronized void checkAllLocales() { 3816 if (aliasMap != null) 3817 return; 3818 3819 boolean useCache = isUnofficial(); // NB: do NOT use the cache if we are 3820 // in official mode. Parsing here 3821 // doesn't take very long (about 3822 // 16s), but 3823 // we want to save some time during development iterations. 3824 // In production, we want the files to be more carefully checked every time. 3825 3826 Hashtable<CLDRLocale, CLDRLocale> aliasMapNew = new Hashtable<>(); 3827 Hashtable<CLDRLocale, String> directionMapNew = new Hashtable<>(); 3828 Set<CLDRLocale> locales = getLocalesSet(); 3829 ElapsedTimer et = new ElapsedTimer(); 3830 CLDRProgressTask progress = openProgress("Parse locales from XML", locales.size()); 3831 try { 3832 File vetdir = getVetdir(); 3833 File xmlCache = new File(vetdir, XML_CACHE_PROPERTIES); 3834 File xmlCacheBack = new File(vetdir, XML_CACHE_PROPERTIES + ".backup"); 3835 Properties xmlCacheProps = new java.util.Properties(); 3836 Properties xmlCachePropsNew = new java.util.Properties(); 3837 if (useCache && xmlCache.exists()) 3838 try { 3839 java.io.FileInputStream is = new java.io.FileInputStream(xmlCache); 3840 xmlCacheProps.load(is); 3841 is.close(); 3842 } catch (java.io.IOException ioe) { 3843 /* throw new UnavailableException */ 3844 SurveyLog.logger.log(java.util.logging.Level.SEVERE, "Couldn't load XML Cache file from '" + "(home)" + "/" 3845 + XML_CACHE_PROPERTIES + ": ", ioe); 3846 busted("Couldn't load XML Cache file from '" + "(home)" + "/" + XML_CACHE_PROPERTIES + ": ", ioe); 3847 return; 3848 } 3849 3850 int n = 0; 3851 int cachehit = 0; 3852 SurveyLog.logger.warning("Parse " + locales.size() + " locales from XML to look for aliases or errors..."); 3853 3854 Set<CLDRLocale> failedSuppTest = new TreeSet<>(); 3855 3856 // Initialize CoverageInfo outside the loop. 3857 CoverageInfo covInfo = CLDRConfig.getInstance().getCoverageInfo(); 3858 for (File f : getInFiles()) { 3859 CLDRLocale loc = fileNameToLocale(f.getName()); 3860 3861 try { 3862 covInfo.getCoverageValue("//ldml", loc.getBaseName()); 3863 } catch (Throwable t) { 3864 SurveyLog.logException(t, "checking SDI for " + loc); 3865 failedSuppTest.add(loc); 3866 } 3867 String locString = loc.toString(); 3868 progress.update(n++, loc.toString()); 3869 try { 3870 String fileHash = fileHash(f); 3871 String aliasTo = null; 3872 String direction = null; 3873 // SurveyLog.logger.warning(fileHash); 3874 3875 String oldHash = xmlCacheProps.getProperty(locString); 3876 if (useCache && oldHash != null && oldHash.equals(fileHash)) { 3877 // cache hit! load from cache 3878 aliasTo = xmlCacheProps.getProperty(locString + ".a", null); 3879 direction = xmlCacheProps.getProperty(locString + ".d", null); 3880 cachehit++; 3881 } else { 3882 Document d = LDMLUtilities.parse(f.getAbsolutePath(), false); 3883 3884 // look for directionality 3885 Node directionalityItem = LDMLUtilities.getNode(d, "//ldml/layout/orientation/characterOrder"); 3886 if (directionalityItem != null) { 3887 direction = LDMLUtilities.getNodeValue(directionalityItem); 3888 if (direction != null && direction.length() > 0) { 3889 } else { 3890 direction = null; 3891 } 3892 } 3893 3894 Node[] aliasItems = LDMLUtilities.getNodeListAsArray(d, "//ldml/alias"); 3895 3896 if ((aliasItems == null) || (aliasItems.length == 0)) { 3897 aliasTo = null; 3898 } else if (aliasItems.length > 1) { 3899 throw new InternalError("found " + aliasItems.length + " items at " + "//ldml/alias" 3900 + " - should have only found 1"); 3901 } else { 3902 aliasTo = LDMLUtilities.getAttributeValue(aliasItems[0], "source"); 3903 } 3904 } 3905 3906 // now, set it into the new map 3907 xmlCachePropsNew.put(locString, fileHash); 3908 if (direction != null) { 3909 directionMapNew.put((loc), direction); 3910 xmlCachePropsNew.put(locString + ".d", direction); 3911 } 3912 if (aliasTo != null) { 3913 aliasMapNew.put((loc), CLDRLocale.getInstance(aliasTo)); 3914 xmlCachePropsNew.put(locString + ".a", aliasTo); 3915 } 3916 } catch (Throwable t) { 3917 SurveyLog.logger.warning("isLocaleAliased: Failed load/validate on: " + loc + " - " + t.toString()); 3918 t.printStackTrace(); 3919 busted("isLocaleAliased: Failed load/validate on: " + loc + " - ", t); 3920 throw new InternalError("isLocaleAliased: Failed load/validate on: " + loc + " - " + t.toString()); 3921 } 3922 } 3923 3924 if (useCache) 3925 try { 3926 // delete old stuff 3927 if (xmlCacheBack.exists()) { 3928 xmlCacheBack.delete(); 3929 } 3930 if (xmlCache.exists()) { 3931 xmlCache.renameTo(xmlCacheBack); 3932 } 3933 java.io.FileOutputStream os = new java.io.FileOutputStream(xmlCache); 3934 xmlCachePropsNew.store(os, "YOU MAY DELETE THIS CACHE. Cache updated at " + new Date()); 3935 progress.update(n++, "Loading configuration.."); 3936 os.close(); 3937 } catch (java.io.IOException ioe) { 3938 /* throw new UnavailableException */ 3939 SurveyLog.logger.log(java.util.logging.Level.SEVERE, "Couldn't write " + xmlCache + " file from '" + cldrHome 3940 + "': ", ioe); 3941 busted("Couldn't write " + xmlCache + " file from '" + cldrHome + "': ", ioe); 3942 return; 3943 } 3944 3945 if (!failedSuppTest.isEmpty()) { 3946 busted("Supplemental Data Test failed on startup for: " + ListFormatter.getInstance().format(failedSuppTest)); 3947 } 3948 3949 SurveyLog.logger.warning("Finished verify+alias check of " + locales.size() + ", " + aliasMapNew.size() 3950 + " aliased locales (" + cachehit + " in cache) found in " + et.toString()); 3951 aliasMap = aliasMapNew; 3952 directionMap = directionMapNew; 3953 } finally { 3954 progress.close(); 3955 } 3956 } 3957 3958 /** 3959 * Is this locale fully aliased? If true, returns what it is aliased to. 3960 */ isLocaleAliased(CLDRLocale id)3961 public synchronized CLDRLocale isLocaleAliased(CLDRLocale id) { 3962 if (aliasMap == null) { 3963 checkAllLocales(); 3964 } 3965 return aliasMap.get(id); 3966 } 3967 getMetazones(String subclass)3968 public Set<String> getMetazones(String subclass) { 3969 Set<String> subSet = new TreeSet<>(); 3970 SupplementalDataInfo supplementalDataInfo = getSupplementalDataInfo(); 3971 for (String zone : supplementalDataInfo.getAllMetazones()) { 3972 if (subclass.equals(supplementalDataInfo.getMetazoneToContinentMap().get(zone))) { 3973 subSet.add(zone); 3974 } 3975 } 3976 return subSet; 3977 } 3978 3979 /** 3980 * This is the bottleneck function for all "main" display pages. 3981 * @param ctx session (contains locale and coverage level, etc) 3982 * @param xpath xpath to use 3983 * @param typeToSubtype (ignored) 3984 * @param b (ignored) 3985 */ showPathList(WebContext ctx, String xpath, String typeToSubtype, boolean b)3986 private void showPathList(WebContext ctx, String xpath, String typeToSubtype, boolean b) { 3987 String vurl = ctx.vurl(ctx.getLocale(), ctx.getPageId(), null, null); 3988 // redirect to /v#... 3989 ctx.redirectToVurl(vurl); 3990 } 3991 3992 /** 3993 * 3994 * @param ctx 3995 * @param xpath 3996 * @param pageId 3997 * 3998 * Called only by showLocale 3999 */ showPathList(WebContext ctx, String xpath, PageId pageId)4000 private void showPathList(WebContext ctx, String xpath, PageId pageId) { 4001 // use the pageid as the xpath 4002 showPathList(ctx, pageId.name(), null, false); 4003 } 4004 4005 private SupplementalDataInfo supplementalDataInfo = null; 4006 getSupplementalDataInfo()4007 public synchronized final SupplementalDataInfo getSupplementalDataInfo() { 4008 if (supplementalDataInfo == null) { 4009 supplementalDataInfo = SupplementalDataInfo.getInstance(getSupplementalDirectory()); 4010 supplementalDataInfo.setAsDefaultInstance(); 4011 } 4012 return supplementalDataInfo; 4013 } 4014 getSupplementalDirectory()4015 public File getSupplementalDirectory() { 4016 return getDiskFactory().getSupplementalDirectory(); 4017 } 4018 4019 private static int pages = 0; 4020 private static int xpages = 0; 4021 4022 /** 4023 * Main setup 4024 */ 4025 static public boolean isSetup = false; 4026 4027 private static ScheduledExecutorService surveyTimer = null; 4028 getTimer()4029 public static synchronized ScheduledExecutorService getTimer() { 4030 if (surveyTimer == null) { 4031 surveyTimer = Executors.newScheduledThreadPool(2); 4032 } 4033 return surveyTimer; 4034 } 4035 4036 /** 4037 * Periodic task for file output 4038 * @param task 4039 * @return 4040 */ addPeriodicTask(Runnable task)4041 public static ScheduledFuture<?> addPeriodicTask(Runnable task) { 4042 final boolean CLDR_QUICK_DAY = CldrUtility.getProperty("CLDR_QUICK_DAY", false); 4043 int firstTime = isUnofficial() ? 15 : 30; 4044 int eachTime = isUnofficial() ? 15 : 15; 4045 4046 if (CLDR_QUICK_DAY && isUnofficial()) { 4047 firstTime = 1; 4048 eachTime = 3; 4049 } 4050 return getTimer().scheduleWithFixedDelay(task, firstTime, eachTime, TimeUnit.MINUTES); 4051 } 4052 addDailyTask(Runnable task)4053 public static ScheduledFuture<?>[] addDailyTask(Runnable task) { 4054 long now = System.currentTimeMillis(); 4055 long next = now; 4056 long period = 24 * 60 * 60 * 1000; // 1 day 4057 Calendar c = com.ibm.icu.util.Calendar.getInstance(TimeZone.getTimeZone(CldrUtility.getProperty("CLDR_TZ", 4058 "America/Los_Angeles"))); 4059 4060 final boolean CLDR_QUICK_DAY = CldrUtility.getProperty("CLDR_QUICK_DAY", false); 4061 4062 if (CLDR_QUICK_DAY && isUnofficial()) { 4063 c.add(Calendar.SECOND, 85); // right away!! 4064 period = 15 * 60 * 1000; // 15 min 4065 } else { 4066 c.add(Calendar.DATE, 1); 4067 c.set(Calendar.HOUR_OF_DAY, 2); 4068 c.set(Calendar.MINUTE, 0); 4069 c.set(Calendar.SECOND, 0); 4070 } 4071 next = c.getTimeInMillis(); 4072 System.err.println("DailyTask- next time is " + ElapsedTimer.elapsedTime(now, next) + " and period is " 4073 + ElapsedTimer.elapsedTime(now, now + period)); 4074 4075 ScheduledFuture<?> o[] = { null, null }; 4076 o[0] = getTimer().schedule(task, 5, TimeUnit.MINUTES); // run one soon 4077 // after startup 4078 o[1] = getTimer().scheduleAtFixedRate(task, next - now, period, TimeUnit.MILLISECONDS); 4079 return o; 4080 } 4081 4082 /** 4083 * Class to startup ST in background and perform background operations. 4084 */ 4085 public transient SurveyThread startupThread = new SurveyThread(this); 4086 4087 /** 4088 * Progress bar manager 4089 */ 4090 private SurveyProgressManager progressManager = new SurveyProgressManager(); 4091 4092 private String cldrHome; 4093 4094 /** 4095 * Startup function. Called from another thread. 4096 * 4097 * @throws ServletException 4098 */ doStartup()4099 public synchronized void doStartup() { 4100 if (isSetup == true) { 4101 return; 4102 } 4103 ElapsedTimer setupTime = new ElapsedTimer(); 4104 CLDRProgressTask progress = openProgress("Main Startup"); 4105 try { 4106 // set up CheckCLDR 4107 4108 progress.update("Initializing Properties"); 4109 4110 CLDRConfig survprops = CLDRConfig.getInstance(); 4111 4112 isConfigSetup = true; 4113 4114 cldrHome = survprops.getProperty("CLDRHOME"); 4115 4116 System.err.println("CLDRHOME=" + cldrHome + ", maint mode=" + isMaintenance()); 4117 4118 stopIfMaintenance(); 4119 4120 progress.update("Setup DB config"); 4121 // set up DB properties 4122 dbUtils.setupDBProperties(this, survprops); 4123 progress.update("Setup phase.."); 4124 4125 // phase 4126 { 4127 Phase newPhase = null; 4128 String phaseString = survprops.getProperty("CLDR_PHASE", null); 4129 try { 4130 if (phaseString != null) { 4131 newPhase = (Phase.valueOf(phaseString)); 4132 } 4133 } catch (IllegalArgumentException iae) { 4134 SurveyLog.logger.warning("Error trying to parse CLDR_PHASE: " + iae.toString()); 4135 } 4136 if (newPhase == null) { 4137 StringBuffer allValues = new StringBuffer(); 4138 for (Phase v : Phase.values()) { 4139 allValues.append(v.name()); 4140 allValues.append(' '); 4141 } 4142 busted("Could not parse CLDR_PHASE - should be one of ( " + allValues + ") but instead got " + phaseString); 4143 } 4144 currentPhase = newPhase; 4145 } 4146 System.out.println("Phase: " + phase() + ", cPhase: " + phase().getCPhase() + ", " + getCurrevCldrApps()); 4147 progress.update("Setup props.."); 4148 newVersion = survprops.getProperty(CLDR_NEWVERSION, CLDR_NEWVERSION); 4149 oldVersion = survprops.getProperty(CLDR_OLDVERSION, CLDR_OLDVERSION); 4150 lastVoteVersion = survprops.getProperty(CLDR_LASTVOTEVERSION, oldVersion); 4151 progress.update("Setup dirs.."); 4152 4153 getVetdir(); 4154 4155 progress.update("Setup vap and message.."); 4156 testpw = survprops.getProperty("CLDR_TESTPW"); // Vet Access 4157 // Password 4158 vap = survprops.getProperty("CLDR_VAP"); // Vet Access Password 4159 if ((vap == null) || (vap.length() == 0)) { 4160 /* throw new UnavailableException */ 4161 busted("No vetting password set. (CLDR_VAP in cldr.properties)"); 4162 return; 4163 } 4164 if ("yes".equals(survprops.getProperty("CLDR_OFFICIAL"))) { 4165 survprops.setEnvironment(CLDRConfig.Environment.PRODUCTION); 4166 } else { 4167 survprops.getEnvironment(); 4168 } 4169 4170 getFileBase(); 4171 getFileBaseSeed(); 4172 4173 // static 4174 // - 4175 // may 4176 // change 4177 // lager 4178 specialMessage = survprops.getProperty("CLDR_MESSAGE"); // not 4179 // static - 4180 // may 4181 // change 4182 // lager 4183 4184 lockOut = survprops.getProperty("CLDR_LOCKOUT"); 4185 4186 if (!new File(fileBase).isDirectory()) { 4187 busted("CLDR_COMMON isn't a directory: " + fileBase); 4188 return; 4189 } 4190 if (!new File(fileBaseSeed).isDirectory()) { 4191 busted("CLDR_SEED isn't a directory: " + fileBaseSeed); 4192 return; 4193 } 4194 progress.update("Setup supplemental.."); 4195 getSupplementalDataInfo(); 4196 4197 try { 4198 // spin up the gears 4199 /* 4200 * TODO: delete this unless it has required side-effects. Formerly assigned to unused variable dcParent. 4201 */ 4202 getSupplementalDataInfo().getBaseFromDefaultContent(CLDRLocale.getInstance("mt_MT")); 4203 } catch (InternalError ie) { 4204 SurveyLog.logger.warning("can't do SupplementalData.defaultContentToParent() - " + ie); 4205 ie.printStackTrace(); 4206 busted("can't do SupplementalData.defaultContentToParent() - " + ie, ie); 4207 } 4208 progress.update("Checking if startup completed.."); 4209 4210 if (isBusted != null) { 4211 return; // couldn't write the log 4212 } 4213 if ((specialMessage != null) && (specialMessage.length() > 0)) { 4214 SurveyLog.logger.warning("SurveyTool with CLDR_MESSAGE: " + specialMessage); 4215 busted("message: " + specialMessage); 4216 } 4217 progress.update("Setup warnings.."); 4218 if (!readWarnings()) { 4219 // already busted 4220 return; 4221 } 4222 4223 progress.update("Setup translation-hints file.."); 4224 4225 // load translation-hints file 4226 getTranslationHintsFile(); 4227 4228 progress.update("Setup translation-hints example.."); 4229 4230 // and example 4231 getTranslationHintsExample(); 4232 4233 progress.update("Wake up the database.."); 4234 4235 doStartupDB(); // will take over progress 50-60 4236 4237 progress.update("Making your Survey Tool happy.."); 4238 4239 if (isBusted == null) { // don't do these if we are already busted 4240 MailSender.getInstance(); 4241 if (!CldrUtility.getProperty("CLDR_NOUPDATE", false)) { 4242 getOutputFileManager().addUpdateTasks(); 4243 } 4244 } else { 4245 progress.update("Not loading mail or output file manager- - SurveyTool already busted."); 4246 } 4247 4248 } catch (Throwable t) { 4249 t.printStackTrace(); 4250 SurveyLog.logException(t, "StartupThread"); 4251 busted("Error on startup: ", t); 4252 } finally { 4253 progress.close(); 4254 } 4255 4256 /** 4257 * Cause locale alias to be checked. 4258 */ 4259 if (!isBusted()) { 4260 isLocaleAliased(CLDRLocale.ROOT); 4261 } 4262 4263 { 4264 CLDRConfig cconfig = CLDRConfig.getInstance(); 4265 SurveyLog.logger 4266 .info("Phase: " + cconfig.getPhase() + " " + getNewVersion() + ", environment: " + cconfig.getEnvironment() + " " + getCurrev(false)); 4267 } 4268 if (!isBusted()) { 4269 SurveyLog.logger.info("------- SurveyTool ready for requests after " + setupTime + "/" + uptime + ". Memory in use: " + usedK() 4270 + "----------------------------\n\n\n"); 4271 isSetup = true; 4272 } else { 4273 SurveyLog.logger.info("------- SurveyTool FAILED TO STARTUP, " + setupTime + "/" + uptime + ". Memory in use: " + usedK() 4274 + "----------------------------\n\n\n"); 4275 } 4276 } 4277 stopIfMaintenance()4278 private static void stopIfMaintenance() { 4279 stopIfMaintenance(null); 4280 } 4281 stopIfMaintenance(HttpServletRequest request)4282 private static void stopIfMaintenance(HttpServletRequest request) { 4283 final File maintFile = getHelperFile(); 4284 final String maintMessage = getMaintMessage(maintFile, request); 4285 if (isMaintenance()) { 4286 if (!maintFile.exists()) { 4287 busted( 4288 "SurveyTool is in setup mode. Please view the main page such as http://127.0.0.1:8080/cldr-apps/survey/ so we can generate a helper file."); 4289 } else { 4290 isBusted = null; // reset busted notice 4291 busted(maintMessage); 4292 } 4293 } 4294 } 4295 getMaintMessage(final File maintFile, HttpServletRequest request)4296 private static String getMaintMessage(final File maintFile, HttpServletRequest request) { 4297 if (!maintFile.exists() && request != null) { 4298 try { 4299 writeHelperFile(request, maintFile); 4300 } catch (IOException e) { 4301 busted("Trying to write helper file " + maintFile.getAbsolutePath(), e); 4302 } 4303 } 4304 if (maintFile.exists()) { 4305 final String maintMessage = "SurveyTool is in setup mode. <br><b>Administrator</b>: Please open the file <a href='file://" 4306 + maintFile.getAbsolutePath() + "'>" + maintFile.getAbsolutePath() + "</a>" 4307 + " for more instructions. <br><b>Users:</b> you must wait until the SurveyTool is back online."; 4308 return maintMessage; 4309 } else { 4310 return null; 4311 } 4312 } 4313 4314 /** 4315 * 4316 * @param request 4317 * @param maintFile 4318 * @throws IOException 4319 * 4320 * Called from cldr-setup.jsp and locally 4321 */ writeHelperFile(HttpServletRequest request, File maintFile)4322 public static synchronized void writeHelperFile(HttpServletRequest request, File maintFile) throws IOException { 4323 CLDRConfigImpl.getInstance().writeHelperFile(request.getScheme() + "://" + request.getServerName() + ":" + 4324 request.getServerPort() + request.getContextPath() + "/", maintFile); 4325 } 4326 4327 /** 4328 * 4329 * @return 4330 * 4331 * Called from cldr-setup.jsp and locally 4332 */ getHelperFile()4333 public static File getHelperFile() { 4334 File maintFile = new File(getSurveyHome(), "admin.html"); 4335 return maintFile; 4336 } 4337 4338 /** 4339 * 4340 * @return 4341 * 4342 * Called from jsp and locally 4343 */ isMaintenance()4344 public static boolean isMaintenance() { 4345 if (!isConfigSetup) return false; // avoid access to CLDRConfig before setup. 4346 CLDRConfig survprops = CLDRConfig.getInstance(); 4347 return survprops.getProperty("CLDR_MAINTENANCE", false); 4348 } 4349 getVetdir()4350 public synchronized File getVetdir() { 4351 if (_vetdir == null) { 4352 CLDRConfig survprops = CLDRConfig.getInstance(); 4353 vetdata = survprops.getProperty("CLDR_VET_DATA", SurveyMain.getSurveyHome() + "/vetdata"); // dir 4354 // for 4355 // vetted 4356 // data 4357 File v = new File(vetdata); 4358 if (!v.isDirectory()) { 4359 v.mkdir(); 4360 SurveyLog.logger.warning("## creating empty vetdir: " + v.getAbsolutePath()); 4361 } 4362 if (!v.isDirectory()) { 4363 busted("CLDR_VET_DATA isn't a directory: " + v); 4364 throw new InternalError("CLDR_VET_DATA isn't a directory: " + v); 4365 } 4366 _vetdir = v; 4367 } 4368 return _vetdir; 4369 } 4370 makeDataDir(String kind)4371 public File makeDataDir(String kind) throws IOException { 4372 File vetdir = getVetdir(); 4373 if (vetdir == null) { 4374 throw new InternalError("vetdir is null."); 4375 } 4376 File dataDir = new File(vetdir, kind); 4377 if (!dataDir.exists()) { 4378 if (!dataDir.mkdirs()) { 4379 throw new IOException("Couldn't create " + dataDir.getAbsolutePath()); 4380 } 4381 } 4382 return dataDir; 4383 } 4384 makeDataDir(String kind, CLDRLocale loc)4385 private File makeDataDir(String kind, CLDRLocale loc) throws IOException { 4386 File dataDir = makeDataDir(kind); // get the parent dir. 4387 4388 // rest of this function is just to determine which subdir (common or 4389 // seed) 4390 4391 Factory f = getDiskFactory(); 4392 File sourceDir = f.getSourceDirectoryForLocale(loc.getBaseName()); 4393 4394 SourceTreeType sourceType = Factory.getSourceTreeType(sourceDir); 4395 DirectoryType dirType = Factory.getDirectoryType(sourceDir); 4396 File subDir = new File(dataDir, sourceType.name()); 4397 if (!subDir.exists()) { 4398 if (!subDir.mkdirs()) { 4399 throw new IOException("Couldn't create " + subDir.getAbsolutePath()); 4400 } 4401 } 4402 File subSubDir = new File(subDir, dirType.name()); 4403 if (!subSubDir.exists()) { 4404 if (!subSubDir.mkdirs()) { 4405 throw new IOException("Couldn't create " + subSubDir.getAbsolutePath()); 4406 } 4407 } 4408 return subSubDir; 4409 } 4410 4411 /** 4412 * 4413 * @param kind 4414 * @param loc 4415 * @return 4416 * @throws IOException 4417 * 4418 * Called from output-status.jsp 4419 */ getDataDir(String kind, CLDRLocale loc)4420 public File getDataDir(String kind, CLDRLocale loc) throws IOException { 4421 return getDataFile(kind, loc).getParentFile(); 4422 } 4423 4424 private Map<Pair<String, CLDRLocale>, File> dirToFile = new HashMap<>(); 4425 4426 /** 4427 * Just get the File. Don't write it. 4428 * 4429 * @param kind 4430 * @param loc 4431 * @return 4432 * @throws IOException 4433 */ getDataFile(String kind, CLDRLocale loc)4434 public synchronized File getDataFile(String kind, CLDRLocale loc) throws IOException { 4435 Pair<String, CLDRLocale> k = new Pair<>(kind, loc); 4436 File f = dirToFile.get(k); 4437 if (f == null) { 4438 f = makeDataFile(kind, loc); 4439 if (f != null) { 4440 dirToFile.put(k, f); 4441 } 4442 } 4443 return f; 4444 } 4445 makeDataFile(String kind, CLDRLocale loc)4446 private File makeDataFile(String kind, CLDRLocale loc) throws IOException { 4447 return new File(makeDataDir(kind, loc), loc.toString() + ".xml"); 4448 } 4449 4450 /** 4451 * Accessed from output-status.jsp and locally 4452 */ 4453 public OutputFileManager outputFileManager = null; 4454 getOutputFileManager()4455 public synchronized OutputFileManager getOutputFileManager() { 4456 if (outputFileManager == null) { 4457 outputFileManager = new OutputFileManager(this); 4458 } 4459 return outputFileManager; 4460 } 4461 isBusted()4462 public static boolean isBusted() { 4463 return (isBusted != null); 4464 } 4465 4466 @Override destroy()4467 public void destroy() { 4468 ElapsedTimer destroyTimer = new ElapsedTimer("SurveyTool destroy()"); 4469 CLDRProgressTask progress = openProgress("shutting down"); 4470 try { 4471 SurveyLog.logger.warning("SurveyTool shutting down.. r" + getCurrevCldrApps()); 4472 if (startupThread != null) { 4473 progress.update("Attempting clean shutdown..."); 4474 startupThread.attemptCleanShutdown(); 4475 } 4476 progress.update("shutting down mail... " + destroyTimer); 4477 MailSender.shutdown(); 4478 if (surveyTimer != null) { 4479 progress.update("Shutting down timer..."); 4480 int patience = 20; 4481 surveyTimer.shutdown(); 4482 Thread.yield(); 4483 while (surveyTimer != null && !surveyTimer.isTerminated()) { 4484 try { 4485 System.err.println("Still Shutting down timer.. " + surveyTimer.toString() + destroyTimer); 4486 if (surveyTimer.awaitTermination(2, TimeUnit.SECONDS)) { 4487 System.err.println("Timer thread is down." + destroyTimer); 4488 surveyTimer = null; 4489 } else { 4490 System.err.println("Timer thread is still running. Attempting TerminateNow." + destroyTimer); 4491 surveyTimer.shutdownNow(); 4492 } 4493 Thread.yield(); 4494 if (--patience < 0) { 4495 System.err.println("=========== patience exceeded. ignoring errant surveyTimer. ==========\n"); 4496 surveyTimer = null; 4497 } 4498 } catch (InterruptedException e) { 4499 // TODO Auto-generated catch block 4500 e.printStackTrace(); 4501 } 4502 } 4503 surveyTimer = null; 4504 System.err.println("Timer thread cancelled." + destroyTimer); 4505 Thread.yield(); 4506 } 4507 progress.update("Shutting down database..." + destroyTimer); 4508 doShutdownDB(); 4509 outputFileManager = null; 4510 progress.update("Destroying servlet..." + destroyTimer); 4511 if (isBusted != null) 4512 isBusted = "servlet destroyed + destroyTimer"; 4513 super.destroy(); 4514 SurveyLog.shutdown(); 4515 } finally { 4516 progress.close(); 4517 System.out.println("------------------- end of SurveyMain.destroy() ------------" + uptime + destroyTimer); 4518 } 4519 } 4520 getXmlFileFilter()4521 private static FileFilter getXmlFileFilter() { 4522 return new FileFilter() { 4523 @Override 4524 public boolean accept(File f) { 4525 String n = f.getName(); 4526 return (!f.isDirectory() && n.endsWith(".xml") && !n.startsWith(".") && !n.startsWith("supplementalData")); 4527 // root is implied, will be included elsewhere. 4528 } 4529 }; 4530 } 4531 4532 /** 4533 * Internal function to get all input files. 4534 * Most functions should use getLocalesSet, etc. 4535 * @return 4536 */ 4537 private static File[] getInFiles() { 4538 Set<File> s = new HashSet<>(); 4539 if (fileBase != null) { 4540 for (File f : getInFiles(fileBase)) { 4541 s.add(f); 4542 } 4543 } 4544 if (fileBaseSeed != null) { 4545 for (File f : getInFiles(fileBaseSeed)) { 4546 s.add(f); 4547 } 4548 } 4549 File arr[] = s.toArray(new File[s.size()]); 4550 return arr; 4551 } 4552 4553 /** 4554 * Only to be used by getInFiles. 4555 * @param base 4556 * @return 4557 */ 4558 private static File[] getInFiles(String base) { 4559 File baseDir = new File(base); 4560 // get the list of input XML files 4561 FileFilter myFilter = getXmlFileFilter(); 4562 return baseDir.listFiles(myFilter); 4563 } 4564 4565 protected static CLDRLocale getLocaleOf(String localeName) { 4566 int dot = localeName.indexOf('.'); 4567 String theLocale = localeName.substring(0, dot); 4568 return CLDRLocale.getInstance(theLocale); 4569 } 4570 4571 private static Set<CLDRLocale> localeListSet = null; 4572 private static Set<CLDRLocale> roLocales = null; 4573 4574 protected static STFactory.LocaleMaxSizer localeSizer; 4575 4576 /** 4577 * Get the list of locales which are read only for some reason. These won't 4578 * be generated, and will be shown with a lock symbol. 4579 * 4580 * @return 4581 */ 4582 public static final synchronized Set<CLDRLocale> getReadOnlyLocales() { 4583 if (roLocales == null) 4584 loadLocalesSet(); 4585 return roLocales; 4586 } 4587 4588 /** 4589 * Get the list of locales that we have seen anywhere. Static set generated 4590 * from {@link #getInFiles()} 4591 * 4592 * @return 4593 */ 4594 public static final synchronized Set<CLDRLocale> getLocalesSet() { 4595 if (localeListSet == null) 4596 loadLocalesSet(); 4597 return localeListSet; 4598 } 4599 4600 /** 4601 * Set up the list of open vs read-only locales, and the full set. 4602 */ 4603 private static synchronized void loadLocalesSet() { 4604 File inFiles[] = getInFiles(); 4605 int nrInFiles = inFiles.length; 4606 Set<CLDRLocale> s = new TreeSet<>(); 4607 Set<CLDRLocale> ro = new TreeSet<>(); 4608 Set<CLDRLocale> w = new TreeSet<>(); 4609 STFactory.LocaleMaxSizer lms = new STFactory.LocaleMaxSizer(); 4610 4611 String onlyLocales = CLDRConfig.getInstance().getProperty("CLDR_ONLY_LOCALES", null); 4612 Set<String> onlySet = null; 4613 4614 if (onlyLocales != null && !onlyLocales.isEmpty()) { 4615 onlySet = new TreeSet<>(); 4616 for (String ol : onlyLocales.split("[ \t]")) { 4617 onlySet.add(ol); 4618 } 4619 } 4620 4621 for (int i = 0; i < nrInFiles; i++) { 4622 String fileName = inFiles[i].getName(); 4623 int dot = fileName.indexOf('.'); 4624 if (dot != -1) { 4625 String locale = fileName.substring(0, dot); 4626 CLDRLocale l = CLDRLocale.getInstance(locale); 4627 s.add(l); // all 4628 SpecialLocales.Type t = (SpecialLocales.getType(l)); 4629 if (t == Type.scratch) { 4630 w.add(l); // always added 4631 } else if (t == Type.readonly || (onlySet != null && !onlySet.contains(locale))) { 4632 ro.add(l); // readonly 4633 } else { 4634 w.add(l); // writeable 4635 } 4636 lms.add(l); 4637 } 4638 } 4639 localeListSet = Collections.unmodifiableSet(s); 4640 roLocales = Collections.unmodifiableSet(ro); 4641 localeSizer = lms; 4642 } 4643 4644 /** 4645 * Array of locales - calculated from {@link #getLocalesSet()} 4646 * 4647 * @return 4648 */ 4649 public static CLDRLocale[] getLocales() { 4650 return getLocalesSet().toArray(new CLDRLocale[0]); 4651 } 4652 4653 /** 4654 * Returns a Map of all interest groups. en -> en, en_US, en_MT, ... fr -> 4655 * fr, fr_BE, fr_FR, ... 4656 */ 4657 private static Map<CLDRLocale, Set<CLDRLocale>> getIntGroups() { 4658 // TODO: rewrite as iterator 4659 CLDRLocale[] locales = getLocales(); 4660 Map<CLDRLocale, Set<CLDRLocale>> h = new HashMap<>(); 4661 for (int i = 0; i < locales.length; i++) { 4662 CLDRLocale locale = locales[i]; 4663 CLDRLocale group = locale; 4664 int dash = locale.toString().indexOf('_'); 4665 if (dash != -1) { 4666 group = CLDRLocale.getInstance(locale.toString().substring(0, dash)); 4667 } 4668 Set<CLDRLocale> s = h.get(group); 4669 if (s == null) { 4670 s = new HashSet<>(); 4671 h.put(group, s); 4672 } 4673 s.add(locale); 4674 } 4675 return h; 4676 } 4677 4678 public boolean isValidLocale(CLDRLocale locale) { 4679 return getLocalesSet().contains(locale); 4680 } 4681 4682 private static int usedK() { 4683 Runtime r = Runtime.getRuntime(); 4684 double total = r.totalMemory(); 4685 total = total / 1024; 4686 double free = r.freeMemory(); 4687 free = free / 1024; 4688 return (int) (Math.floor(total - free)); 4689 } 4690 4691 public static void busted(String what) { 4692 busted(what, null, null); 4693 } 4694 4695 /** 4696 * Report an error with a SQLException 4697 * 4698 * @param what 4699 * the error 4700 * @param se 4701 * the SQL Exception 4702 */ 4703 protected static void busted(String what, SQLException se) { 4704 busted(what, se, DBUtils.unchainSqlException(se)); 4705 } 4706 4707 protected static void busted(String what, Throwable t) { 4708 if (t instanceof SQLException) { 4709 busted(what, (SQLException) t); 4710 } else { 4711 busted(what, t, getThrowableStack(t)); 4712 } 4713 } 4714 4715 /** 4716 * mark as busted, with no special logging. This is called by the SurveyLog to make sure an out of memory marks things as down. 4717 * @param t 4718 */ 4719 public static void markBusted(Throwable t) { 4720 markBusted(t.toString(), t, StackTracker.stackToString(t.getStackTrace(), 0)); 4721 } 4722 4723 /** 4724 * log that the survey tool is down. 4725 * @param what 4726 * @param t 4727 * @param stack 4728 */ 4729 private static void busted(String what, Throwable t, String stack) { 4730 if (t != null) { 4731 SurveyLog.logException(t, what /* , ignore stack - fetched from exception */); 4732 } 4733 SurveyLog.logger.warning("SurveyTool " + SurveyMain.getCurrevCldrApps() + " busted: " + what + " ( after " + pages + "html+" + xpages 4734 + "xml pages served, " 4735 + getGuestsAndUsers() + ")"); 4736 System.err.println("Busted at stack: \n" + StackTracker.currentStack()); 4737 markBusted(what, t, stack); 4738 SurveyLog.logger.severe(what); 4739 } 4740 4741 /** 4742 * Mark busted, but don't log it 4743 * @param what 4744 * @param t 4745 * @param stack 4746 */ 4747 public static void markBusted(String what, Throwable t, String stack) { 4748 SurveyLog.warnOnce("******************** SurveyTool is down (busted) ********************"); 4749 if (!isBusted()) { // Keep original failure message. 4750 isBusted = what; 4751 if (stack == null) { 4752 if (t != null) { 4753 stack = StackTracker.stackToString(t.getStackTrace(), 0); 4754 } else { 4755 stack = "(no stack)\n"; 4756 } 4757 } 4758 isBustedStack = stack + "\n" + "[" + new Date().toGMTString() + "] "; //isBustedThrowable = t; 4759 isBustedTimer = new ElapsedTimer(); 4760 } else { 4761 SurveyLog.warnOnce("[was already busted, not overriding old message.]"); 4762 } 4763 } 4764 4765 private static long shortN = 0; 4766 private static final int MAX_CHARS = 100; 4767 private static final String SHORT_A = "(Click to show entire message.)"; 4768 private static final String SHORT_B = "(hide.)"; 4769 4770 public static final String QUERY_FIELDHASH = "fhash"; 4771 4772 private static String getShortened(String str) { 4773 return getShortened(str, MAX_CHARS); 4774 } 4775 4776 private static synchronized String getShortened(String str, int max) { 4777 if (str.length() < (max + 1 + SHORT_A.length())) { 4778 return (str); 4779 } else { 4780 int cutlen = max; 4781 String key = CookieSession.cheapEncode(shortN++); 4782 int newline = str.indexOf('\n'); 4783 if ((newline > 2) && (newline < cutlen)) { 4784 cutlen = newline; 4785 } 4786 newline = str.indexOf("Exception:"); 4787 if ((newline > 2) && (newline < cutlen)) { 4788 cutlen = newline; 4789 } 4790 newline = str.indexOf("Message:"); 4791 if ((newline > 2) && (newline < cutlen)) { 4792 cutlen = newline; 4793 } 4794 newline = str.indexOf("<br>"); 4795 if ((newline > 2) && (newline < cutlen)) { 4796 cutlen = newline; 4797 } 4798 newline = str.indexOf("<p>"); 4799 if ((newline > 2) && (newline < cutlen)) { 4800 cutlen = newline; 4801 } 4802 return getShortened(str.substring(0, cutlen), str, key); 4803 } 4804 } 4805 4806 private static String getShortened(String shortStr, String longStr, String warnHash) { 4807 return ("<span id='h_ww" + warnHash + "'>" + shortStr + "... ") 4808 + ("<a href='javascript:show(\"ww" + warnHash + "\")'>" + SHORT_A + "</a></span>") 4809 + ("<!-- <noscript>Warning: </noscript> -->" + "<span style='display: none' id='ww" + warnHash + "'>" + longStr 4810 + "<a href='javascript:hide(\"ww" + warnHash + "\")'>" + SHORT_B + "</a></span>"); 4811 } 4812 4813 private Hashtable<String, String> xpathWarnings = new Hashtable<>(); 4814 4815 private boolean readWarnings() { 4816 try { 4817 BufferedReader in = FileUtilities.openUTF8Reader(cldrHome, "surveyInfo.txt"); 4818 String line; 4819 while ((line = in.readLine()) != null) { 4820 if ((line.length() <= 0) || (line.charAt(0) == '#')) { 4821 continue; 4822 } 4823 String[] result = line.split("\t"); 4824 xpathWarnings.put(result[0] + " /" + result[1], result[2]); 4825 } 4826 } catch (java.io.FileNotFoundException t) { 4827 return true; 4828 } catch (java.io.IOException t) { 4829 SurveyLog.logger.warning(t.toString()); 4830 t.printStackTrace(); 4831 busted("Error: trying to read xpath warnings file. " + cldrHome + "/surveyInfo.txt"); 4832 return true; 4833 } 4834 return true; 4835 } 4836 4837 public DBUtils dbUtils = null; 4838 4839 private void doStartupDB() { 4840 if (isMaintenance()) { 4841 throw new InternalError("SurveyTool is in setup mode."); 4842 } 4843 CLDRProgressTask progress = openProgress("Database Setup"); 4844 try { 4845 progress.update("begin.."); // restore 4846 dbUtils.startupDB(this, progress); 4847 // now other tables.. 4848 progress.update("Setup databases "); // restore 4849 try { 4850 progress.update("Setup " + UserRegistry.CLDR_USERS); // restore 4851 progress.update("Create UserRegistry " + UserRegistry.CLDR_USERS); // restore 4852 reg = UserRegistry.createRegistry(SurveyLog.logger, this); 4853 } catch (SQLException e) { 4854 busted("On UserRegistry startup", e); 4855 return; 4856 } 4857 progress.update("Create XPT"); // restore 4858 try { 4859 xpt = XPathTable.createTable(dbUtils.getDBConnection()); 4860 } catch (SQLException e) { 4861 busted("On XPathTable startup", e); 4862 return; 4863 } 4864 4865 progress.update("Load XPT"); 4866 System.err.println("XPT ready with " + xpt.statistics()); 4867 xpt.loadXPaths(getDiskFactory().makeSource(TRANS_HINT_ID)); 4868 System.err.println("XPT spun up with " + xpt.statistics()); 4869 progress.update("Create fora"); // restore 4870 try { 4871 fora = SurveyForum.createTable(SurveyLog.logger, dbUtils.getDBConnection(), this); 4872 } catch (SQLException e) { 4873 busted("On Fora startup", e); 4874 return; 4875 } 4876 progress.update(" DB setup complete."); // restore 4877 } finally { 4878 progress.close(); 4879 } 4880 } 4881 4882 private static final String getThrowableStack(Throwable t) { 4883 try { 4884 StringWriter asString = new StringWriter(); 4885 t.printStackTrace(new PrintWriter(asString)); 4886 return asString.toString(); 4887 } catch (Throwable tt) { 4888 tt.printStackTrace(); 4889 return ("[[unable to get stack: " + tt.toString() + "]]"); 4890 } 4891 } 4892 4893 private void doShutdownDB() { 4894 try { 4895 closeOpenUserLocaleStuff(true); 4896 4897 // shut down other connections 4898 try { 4899 CookieSession.shutdownDB(); 4900 } catch (Throwable t) { 4901 t.printStackTrace(); 4902 SurveyLog.logger.warning("While shutting down cookiesession "); 4903 } 4904 try { 4905 if (reg != null) 4906 reg.shutdownDB(); 4907 } catch (Throwable t) { 4908 t.printStackTrace(); 4909 SurveyLog.logger.warning("While shutting down reg "); 4910 } 4911 if (dbUtils != null) { 4912 dbUtils.doShutdown(); 4913 } 4914 dbUtils = null; 4915 } catch (SQLException se) { 4916 SurveyLog.logger.info("DB: while shutting down: " + se.toString()); 4917 } 4918 } 4919 4920 private void closeOpenUserLocaleStuff(boolean closeAll) { 4921 if (allUserLocaleStuffs.isEmpty()) 4922 return; 4923 SurveyLog.logger.warning("Closing " + allUserLocaleStuffs.size() + " user files."); 4924 for (UserLocaleStuff uf : allUserLocaleStuffs) { 4925 if (!uf.isClosed()) { 4926 uf.internalClose(); 4927 } 4928 } 4929 } 4930 4931 // ====== Utility Functions 4932 4933 /** 4934 * 4935 * @param a 4936 * @return 4937 * 4938 * Called from AdminAjax.jsp and locally 4939 */ 4940 public static final String timeDiff(long a) { 4941 return timeDiff(a, System.currentTimeMillis()); 4942 } 4943 4944 public static final String durationDiff(long a) { 4945 return timeDiff(System.currentTimeMillis() - a); 4946 } 4947 4948 private static final String timeDiff(long a, long b) { 4949 final long ONE_DAY = 86400 * 1000; 4950 final long A_LONG_TIME = ONE_DAY * 3; 4951 if ((b - a) > (A_LONG_TIME)) { 4952 double del = (b - a); 4953 del /= ONE_DAY; 4954 int days = (int) del; 4955 return days + " days"; 4956 } else { 4957 // round to even second, to avoid ElapsedTimer bug 4958 a -= (a % 1000); 4959 b -= (b % 1000); 4960 return ElapsedTimer.elapsedTime(a, b); 4961 } 4962 } 4963 4964 public static String shortClassName(Object o) { 4965 try { 4966 String cls = o.getClass().toString(); 4967 int io = cls.lastIndexOf("."); 4968 if (io != -1) { 4969 cls = cls.substring(io + 1, cls.length()); 4970 } 4971 return cls; 4972 } catch (NullPointerException n) { 4973 return null; 4974 } 4975 } 4976 4977 /** 4978 * get the local host 4979 */ 4980 public static String localhost() { 4981 try { 4982 return InetAddress.getLocalHost().getHostName(); 4983 } catch (Exception e) { 4984 return "UNKNOWN"; 4985 } 4986 } 4987 4988 public static String bugFeedbackUrl(String subject) { 4989 return BUG_URL_BASE + "/newticket?component=survey&summary=" + java.net.URLEncoder.encode(subject); 4990 } 4991 4992 // ============= Following have to do with phases 4993 4994 public static boolean isPhaseVetting() { 4995 return phase() == Phase.VETTING; 4996 } 4997 4998 public static boolean isPhaseVettingClosed() { 4999 return phase() == Phase.VETTING_CLOSED; 5000 } 5001 5002 public static boolean isPhaseClosed() { 5003 return (phase() == Phase.CLOSED) || (phase() == Phase.VETTING_CLOSED); 5004 } 5005 5006 public static boolean isPhaseReadonly() { 5007 return phase() == Phase.READONLY; 5008 } 5009 5010 public static boolean isPhaseBeta() { 5011 return phase() == Phase.BETA; 5012 } 5013 5014 public static final Phase phase() { 5015 return currentPhase; 5016 } 5017 5018 public static String getOldVersion() { 5019 return oldVersion; 5020 } 5021 5022 /** 5023 * The last version where there was voting. CLDR_LASTVOTEVERSION 5024 * @return 5025 */ 5026 public static String getLastVoteVersion() { 5027 return lastVoteVersion; 5028 } 5029 5030 public static String getNewVersion() { 5031 return newVersion; 5032 } 5033 5034 public static String getVotesAfterString() { 5035 return CLDRConfig.getInstance().getProperty(SurveyMain.CLDR_NEWVERSION_AFTER, SurveyMain.NEWVERSION_EPOCH); 5036 } 5037 5038 public static Date getVotesAfterDate() { 5039 return new Date(Timestamp.valueOf(getVotesAfterString()).getTime()); 5040 } 5041 5042 static String xmlescape(String str) { 5043 if (str.indexOf('&') >= 0) { 5044 return str.replaceAll("&", "\\&"); 5045 } else { 5046 return str; 5047 } 5048 } 5049 5050 @Override 5051 public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException { 5052 STFactory.unimp(); // do not call 5053 } 5054 5055 @Override 5056 public void writeExternal(ObjectOutput arg0) throws IOException { 5057 STFactory.unimp(); // do not call 5058 } 5059 5060 /** 5061 * Format and display the system's default timezone. 5062 * @return 5063 */ 5064 public static String defaultTimezoneInfo() { 5065 return new SimpleDateFormat("VVVV: ZZZZ", SurveyMain.TRANS_HINT_LOCALE).format(System.currentTimeMillis()); 5066 } 5067 5068 private static CLDRFile gEnglishFile = null; 5069 5070 /** 5071 * Get exactly the "en" disk file. 5072 * @see #getTranslationHintsFile() 5073 * @return 5074 */ 5075 public CLDRFile getEnglishFile() { 5076 if (gEnglishFile == null) synchronized (this) { 5077 CLDRFile english = getDiskFactory().make(ULocale.ENGLISH.getBaseName(), true); 5078 english.setSupplementalDirectory(getSupplementalDirectory()); 5079 english.freeze(); 5080 gEnglishFile = english; 5081 } 5082 return gEnglishFile; 5083 } 5084 } 5085