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 "&nbsp;" + 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             + "&amp;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 + "&amp;see=" + cs.id + "'>"
1201             + ctx.iconHtml("zoom", "SEE this user") + "see" + "</a> |");
1202         ctx.println("<a href='" + ctx.base() + "?&amp;s=" + cs.id + "'>" + "be" + "</a> |");
1203         ctx.println("<a href='" + ctx.base() + "?dump=" + vap + "&amp;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", "&nbsp;&nbsp;&nbsp;").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") + "&amp;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("&nbsp;-&nbsp;<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>" + "&nbsp; <span style='font-size: 80%' align='right'>"
2524                         + UserRegistry.levelToStr(ctx, theirLevel).replaceAll(" ", "&nbsp;") + "</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("&", "&amp;").replaceAll("'", "&quot;") + "'>");
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             + "&nbsp;/&nbsp;"
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&amp;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("&", "\\&amp;");
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