package hirondelle.web4j.readconfig;

import hirondelle.web4j.database.ConnectionSource;
import hirondelle.web4j.database.TxIsolationLevel;
import hirondelle.web4j.util.Util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.regex.Pattern;

/**
 (UNPUBLISHED) Access the config values needed by the framework.
 
 <P>Assumptions:
 <ul> 
  <li><tt>init</tt> is called only once, upon startup, when the system is in single-threaded mode
  <li>there is no re-init or reload mechanism
  <li>after <tt>init</tt> is called, the data will never change, and this class is safe to be used 
  in a multi-threaded environment
  <li>all data returned by the getXXX methods is immutable, or a defensive copy is passed
 </ul>  
 
 <P>The typical user of this class is simple:
<pre>Config config = new Config();
  String blah = config.getBlah;
</pre>
 The caller usually has no need to store the data in their own static private field.
 
  <P>The hard-coding in this class is desirable and necessary. Note that it makes no 
  sense to add config items at runtime, since no code will be able to talk to it. 
  
  <P>Default values are defined internally by this class. Callers performing tests 
  may use default values, or they may override any number of default values by calling <tt>init</tt>.
  There is no method to revert to the original defaults.
  
  <P>
   When an app as a whole is reloaded, it's a full reload, with new classes/classloaders.
   When the Deployment Descriptor (DD) is touched, often the app reloads partially, with the same classes/classloaders, 
   but a new Servlet object. (This depends on the Container.) 
   Thus, static blocks are not re-executed in that case.
    
   <P>This means that classes should not copy/store data from Config in a static block, since that static block
   will not see partial reloads (tweaks to the DD). This is a bit tricky.
   As well, any 'first-use-initialization' logic in class X cannot talk to the config, since it will not be executed on a partial reload.
   
   <P>If all the parsing/caching is done by this class, then callers don't have to worry about partial reloads, and whether or 
   not they are out of date. In addition, if the syntax is the same, then the parsing is centralized here.
*/
public final class Config {

  /** Value - {@value} - special value for some settings, denoting an absent value. */
  public static final String NONE = "NONE";
  
   /* NOTE: Most of this code was generated by a one-off script. */
  
  /**
   Must be called once and only once, upon startup, when the system is in 
   single-threaded mode. Throws an exception if a problem with the syntax is detected.
   
   <P>Note than this takes a generic map, not <tt>ServletContext</tt>. That's important, 
   since it allows this Config to be used in any context, not just a servlet container.
   <P>The key is case-sensitive.
  */
  public static void init(Map<String, String> aKeyValuePairs){
    initAllItems(aKeyValuePairs);
  }

  /** 
   Confirm that database settings in <tt>web.xml</tt> are known to {@link ConnectionSource}.
   Order dependency: this method is called after <tt>init</tt>, since it needs to know the database names, 
   which in turn requires a call to BuildImpl.init.   
  */
  public static void checkDbNamesInSettings(Set<String> aValidDbNames){
    Set<String> namesInSettings = new LinkedHashSet<String>(); 
    
    namesInSettings.addAll(fHasAutoGeneratedKeys.getDbNames());
    namesInSettings.addAll(fSqlFetcherDefaultTxIsolationLevel.getDbNames());
    namesInSettings.addAll(fSqlEditorDefaultTxIsolationLevel.getDbNames());
    namesInSettings.addAll(fErrorCodeForDuplicateKey.getDbNames());
    namesInSettings.addAll(fErrorCodeForForeignKey.getDbNames());
    namesInSettings.addAll(fMaxRows.getDbNames());
    namesInSettings.addAll(fFetchSize.getDbNames());
    namesInSettings.addAll(fIsSQLPrecompilationAttempted.getDbNames());
    namesInSettings.addAll(fDateTimeFormatForPassingParamsToDb.getDbNames());
    
    Set<String> unknownNames = new LinkedHashSet<String>();
    for(String name: namesInSettings){
      if(Util.textHasContent(name) && ! aValidDbNames.contains(name)){
        unknownNames.add(name);
      }
    }
    
    if(! unknownNames.isEmpty() ) {
       throw new IllegalArgumentException("Web.xml contains settings that refer to databases that are not known to your implementation of ConnectionSource.getDatabaseNames(). Please check spelling and case for : " + Util.logOnePerLine(unknownNames));
    }
  }
  
  /** Intended for logging and trouble tickets. */
  public Map<String, String> getRawMap(){
    return Collections.unmodifiableMap(fMap);
  }
  
  public Boolean isTesting(){
    return fIsTesting;
  }
  
  /** Value is present, and is not 'NONE'. */
  public boolean isEnabled(String aValue){
    return Util.textHasContent(aValue) && ! NONE.equalsIgnoreCase(aValue);
  }
  
  public String getWebmaster(){
    return fWebmaster;
  }
  
  public String getImplicitMappingRemoveBasePackage(){
    return fImplicitMappingRemoveBasePackage;
  }
  
  public String getMailServerConfig(){
    return fMailServerConfig;
  }

  public String getMailServerCredentials(){
    return fMailServerCredentials;
  }

  public String getLoggingDirectory (){
    return fLoggingDirectory;
  }

  public List<String> getLoggingLevels(){
    return fLoggingLevels;
  }

  public List<String> getTroubleTicketMailingList(){
    return Collections.unmodifiableList(fTroubleTicketMailingList);
  }
  
  public Long getMinimumIntervalBetweenTroubleTickets(){
    return fMinimumIntervalBetweenTroubleTickets;
  }

  /** Returns the value in nanos, not seconds. */
  public Long getPoorPerformanceThreshold(){
    long NANOSECONDS_PER_SECOND = 1000 *1000 * 1000;
    return fPoorPerformanceThreshold  * NANOSECONDS_PER_SECOND;
  }

  public Long getMaxHttpRequestSize(){
    return fMaxHttpRequestSize;
  }

  public Long getMaxFileUploadRequestSize(){
    return fMaxFileUploadRequestSize;
  }

  public Long getMaxRequestParamValueSize(){
    return fMaxRequestParamValueSize;
  }

  public Boolean getSpamDetectionInFirewall(){
    return fSpamDetectionInFirewall;
  }

  public Boolean getFullyValidateFileUploads(){
    return fFullyValidateFileUploads;
  }

  public Boolean getAllowStringAsBuildingBlock(){
    return fAllowStringAsBuildingBlock;
  }

  public Map<String, List<String>> getUntrustedProxyForUserId(){
    return fUntrustedProxyForUserId;
  }

  public String getCharacterEncoding(){
    return fCharacterEncoding;
  }

  public Locale getDefaultLocale(){
    return fDefaultLocale;
  }

  public TimeZone getDefaultUserTimeZone(){
    return TimeZone.getTimeZone(fDefaultUserTimeZone.getID()); //mutable class
  }

  public Boolean hasTimeZoneHint(){ 
    return fTimeZoneHint != null;
  }
  
  public Calendar getTimeZoneHint(){
    Calendar result = null;
    if( hasTimeZoneHint() ) {
      result = Calendar.getInstance(fTimeZoneHint);
    }
    return result;
  }

  //this could be deleted ? - no callers
  public String getDecimalSeparator(){
    return fDecimalSeparator;
  }
  
  public Pattern getDecimalInputPattern(){
    return fDecimalInputPattern; //derived from decimal separator
  }

  public String getBigDecimalDisplayFormat(){
    return fBigDecimalDisplayFormat;
  }

  public String getBooleanTrueDisplayFormat(){
    return fBooleanTrueDisplayFormat;
  }

  public String getBooleanFalseDisplayFormat(){
    return fBooleanFalseDisplayFormat;
  }

  public String getEmptyOrNullDisplayFormat(){
    return fEmptyOrNullDisplayFormat;
  }

  public String getIntegerDisplayFormat(){
    return fIntegerDisplayFormat;
  }

  public String getIgnorableParamValue(){
    return fIgnorableParamValue;
  }

  public Boolean getIsSQLPrecompilationAttempted(String aDbName){
    return asBoolean(fIsSQLPrecompilationAttempted.getValue(aDbName));
  }

  public Integer getMaxRows(String aDbName){
    return asInteger(fMaxRows.getValue(aDbName));
  }

  public Integer getFetchSize(String aDbName){
    return asInteger(fFetchSize.getValue(aDbName));
  }

  public Boolean getHasAutoGeneratedKeys(String aDbName){
    return asBoolean(fHasAutoGeneratedKeys.getValue(aDbName));
  }

  public List<Integer> getErrorCodesForDuplicateKey(String aDbName){
    return asIntegerList(fErrorCodeForDuplicateKey.getValue(aDbName));
  }

  public List<Integer> getErrorCodesForForeignKey(String aDbName){
    return asIntegerList(fErrorCodeForForeignKey.getValue(aDbName));
  }

  public TxIsolationLevel getSqlFetcherDefaultTxIsolationLevel(String aDbName){
    return TxIsolationLevel.valueOf(fSqlFetcherDefaultTxIsolationLevel.getValue(aDbName));
  }

  public TxIsolationLevel getSqlEditorDefaultTxIsolationLevel(String aDbName){
    return TxIsolationLevel.valueOf(fSqlEditorDefaultTxIsolationLevel.getValue(aDbName));
  }

  public String getDateFormat(String aDbName){
    return asDateTimeFormat(fDateTimeFormatForPassingParamsToDb.getValue(aDbName), 0);
  }
  
  public String getTimeFormat(String aDbName){
    return asDateTimeFormat(fDateTimeFormatForPassingParamsToDb.getValue(aDbName), 1);
  }
  
  public String getDateTimeFormat(String aDbName){
    return asDateTimeFormat(fDateTimeFormatForPassingParamsToDb.getValue(aDbName), 2);
  }

  // PRIVATE 
  
  private static List<String> fErrors = new ArrayList<String>();
  
  private static void initAllItems(Map<String, String> aKeyValuePairs) {
    fMap = aKeyValuePairs;
    
    fWebmaster = initThisStringNoDefault("Webmaster", aKeyValuePairs);
    fImplicitMappingRemoveBasePackage = initThisStringNoDefault("ImplicitMappingRemoveBasePackage", aKeyValuePairs);
    
    if (hasValue("MailServerConfig", aKeyValuePairs)) {
      fMailServerConfig = initThisString("MailServerConfig", aKeyValuePairs);
    }
    if (hasValue("MailServerCredentials", aKeyValuePairs)) {
      fMailServerCredentials = initThisString("MailServerCredentials", aKeyValuePairs);
    }
    if (hasValue("LoggingDirectory", aKeyValuePairs)) {
      fLoggingDirectory = initThisString("LoggingDirectory", aKeyValuePairs);
    }
    if (hasValue("LoggingLevels", aKeyValuePairs)) {
      fLoggingLevels = initThisStringList("LoggingLevels", aKeyValuePairs);
    }
    if (hasValueNotNone("TroubleTicketMailingList", aKeyValuePairs)) {
      fTroubleTicketMailingList = initThisStringList("TroubleTicketMailingList", aKeyValuePairs);
    }
    if (hasValue("MinimumIntervalBetweenTroubleTickets", aKeyValuePairs)) {
      fMinimumIntervalBetweenTroubleTickets = initThisLong("MinimumIntervalBetweenTroubleTickets", aKeyValuePairs);
    }
    if (hasValue("PoorPerformanceThreshold", aKeyValuePairs)) {
      fPoorPerformanceThreshold = initThisLong("PoorPerformanceThreshold", aKeyValuePairs);
    }
    if (hasValue("MaxHttpRequestSize", aKeyValuePairs)) {
      fMaxHttpRequestSize = initThisLong("MaxHttpRequestSize", aKeyValuePairs);
      checkTooLow(fMaxHttpRequestSize, 1000L, "MaxHttpRequestSize");
    }
    if (hasValue("MaxFileUploadRequestSize", aKeyValuePairs)) {
      fMaxFileUploadRequestSize = initThisLong("MaxFileUploadRequestSize", aKeyValuePairs);
      checkTooLow(fMaxFileUploadRequestSize, 1000L, "MaxFileUploadRequestSize");
    }
    if (hasValue("MaxRequestParamValueSize", aKeyValuePairs)) {
      fMaxRequestParamValueSize = initThisLong("MaxRequestParamValueSize", aKeyValuePairs);
      checkTooLow(fMaxRequestParamValueSize, 1000L, "MaxRequestParamValueSize");
    }
    if (hasValue("SpamDetectionInFirewall", aKeyValuePairs)) {
      fSpamDetectionInFirewall = initThisBoolean("SpamDetectionInFirewall", aKeyValuePairs);
    }
    if (hasValue("FullyValidateFileUploads", aKeyValuePairs)) {
      fFullyValidateFileUploads = initThisBoolean("FullyValidateFileUploads", aKeyValuePairs);
    }
    if (hasValue("AllowStringAsBuildingBlock", aKeyValuePairs)) {
      fAllowStringAsBuildingBlock = initThisBoolean("AllowStringAsBuildingBlock", aKeyValuePairs);
    }
    if (hasValue("UntrustedProxyForUserId", aKeyValuePairs)) {
      ParseUntrustedProxy untrusted = new ParseUntrustedProxy();
      fUntrustedProxyForUserId = untrusted.parse(aKeyValuePairs.get("UntrustedProxyForUserId"));
    }
    if (hasValue("CharacterEncoding", aKeyValuePairs)) {
      fCharacterEncoding = initThisString("CharacterEncoding", aKeyValuePairs);
    }
    if (hasValue("DefaultLocale", aKeyValuePairs)) {
      fDefaultLocale = initThisLocale("DefaultLocale", aKeyValuePairs);
    }
    if (hasValue("DefaultUserTimeZone", aKeyValuePairs)) {
      fDefaultUserTimeZone = initThisTimeZone("DefaultUserTimeZone", aKeyValuePairs);
    }
    if (hasValueNotNone("TimeZoneHint", aKeyValuePairs)) {
      fTimeZoneHint = initThisTimeZone("TimeZoneHint", aKeyValuePairs);
    }
    if (hasValue("DecimalSeparator", aKeyValuePairs)) {
      fDecimalSeparator = initThisString("DecimalSeparator", aKeyValuePairs);
      fDecimalInputPattern = fDecimalParser.getDecimalFormatPattern(fDecimalSeparator);
    }
    if (hasValue("BigDecimalDisplayFormat", aKeyValuePairs)) {
      fBigDecimalDisplayFormat = initThisString("BigDecimalDisplayFormat", aKeyValuePairs);
    }
    if (hasValue("BooleanTrueDisplayFormat", aKeyValuePairs)) {
      fBooleanTrueDisplayFormat = initThisString("BooleanTrueDisplayFormat", aKeyValuePairs);
    }
    if (hasValue("BooleanFalseDisplayFormat", aKeyValuePairs)) {
      fBooleanFalseDisplayFormat = initThisString("BooleanFalseDisplayFormat", aKeyValuePairs);
    }
    if (hasValue("EmptyOrNullDisplayFormat", aKeyValuePairs)) {
      fEmptyOrNullDisplayFormat = initThisString("EmptyOrNullDisplayFormat", aKeyValuePairs);
    }
    if (hasValue("IntegerDisplayFormat", aKeyValuePairs)) {
      fIntegerDisplayFormat = initThisString("IntegerDisplayFormat", aKeyValuePairs);
    }
    if (hasValue("IgnorableParamValue", aKeyValuePairs)) {
      fIgnorableParamValue = initThisString("IgnorableParamValue", aKeyValuePairs);
    }
    if (hasValue("IsSQLPrecompilationAttempted", aKeyValuePairs)) {
      fIsSQLPrecompilationAttempted = initThisDbConfig("IsSQLPrecompilationAttempted", aKeyValuePairs);
    }
    if (hasValue("MaxRows", aKeyValuePairs)) {
      fMaxRows = initThisDbConfig("MaxRows", aKeyValuePairs);
    }
    if (hasValue("FetchSize", aKeyValuePairs)) {
      fFetchSize = initThisDbConfig("FetchSize", aKeyValuePairs);
    }
    if (hasValue("HasAutoGeneratedKeys", aKeyValuePairs)) {
      fHasAutoGeneratedKeys = initThisDbConfig("HasAutoGeneratedKeys", aKeyValuePairs);
    }
    if (hasValue("ErrorCodeForDuplicateKey", aKeyValuePairs)) {
      fErrorCodeForDuplicateKey = initThisDbConfig("ErrorCodeForDuplicateKey", aKeyValuePairs);
    }
    if (hasValue("ErrorCodeForForeignKey", aKeyValuePairs)) {
      fErrorCodeForForeignKey = initThisDbConfig("ErrorCodeForForeignKey", aKeyValuePairs);
    }
    if (hasValue("SqlFetcherDefaultTxIsolationLevel", aKeyValuePairs)) {
      fSqlFetcherDefaultTxIsolationLevel = initThisDbConfig("SqlFetcherDefaultTxIsolationLevel", aKeyValuePairs);
    }
    if (hasValue("SqlEditorDefaultTxIsolationLevel", aKeyValuePairs)) {
      fSqlEditorDefaultTxIsolationLevel = initThisDbConfig("SqlEditorDefaultTxIsolationLevel", aKeyValuePairs);
    }
    if (hasValue("DateTimeFormatForPassingParamsToDb", aKeyValuePairs)) {
      fDateTimeFormatForPassingParamsToDb = initThisDbConfig("DateTimeFormatForPassingParamsToDb", aKeyValuePairs);
    }
    if (hasValue("IsTesting", aKeyValuePairs)) {
      fIsTesting = initThisBoolean("IsTesting", aKeyValuePairs);
    }
    
    if (! fErrors.isEmpty()){
      throw new RuntimeException("Errors in config data: " + fErrors);
    }
  }
  
  private static boolean hasValue(String aName,  Map<String, String> aKeyValuePairs){
    return Util.textHasContent(aKeyValuePairs.get(aName));    
  }
  
  private static boolean hasValueNotNone(String aName,  Map<String, String> aKeyValuePairs){
    String value = aKeyValuePairs.get(aName);
    return Util.textHasContent(value) && (! NONE.equalsIgnoreCase(value));    
  }

  private static String initThisString(String aName, Map<String, String> aKeyValuePairs){
    return aKeyValuePairs.get(aName);
  }
  
  private static Long initThisLong(String aName, Map<String, String> aKeyValuePairs){
    return asLong(aKeyValuePairs.get(aName));
  }
  
  private static TimeZone initThisTimeZone(String aName, Map<String, String> aKeyValuePairs){
    return asTimeZone(aKeyValuePairs.get(aName));
  }
  
  private static List<String> initThisStringList(String aName, Map<String, String> aKeyValuePairs){
    return asStringListWithNone(aKeyValuePairs.get(aName));
  }
  
  private static String initThisStringNoDefault(final String aName, final Map<String, String> aKeyValuePairs){
    String result = aKeyValuePairs.get(aName);
    if (! Util.textHasContent(result)){
      fErrors.add(aName + " has no value set.");
    }
    return result;
  }
  
  private static Boolean initThisBoolean(String aName, Map<String, String> aKeyValuePairs){
    String value = aKeyValuePairs.get(aName);
    return Util.parseBoolean(value);
  }
  
  private static DbConfigParser initThisDbConfig(String aName, Map<String, String> aKeyValuePairs){
    return new DbConfigParser(aKeyValuePairs.get(aName));
  }

  private static Locale initThisLocale(String aName, Map<String, String> aKeyValuePairs){
    return asLocale(aKeyValuePairs.get(aName));
  }
  
  /* These declares define the default value of each item, if any. */

  //used for iteration over raw values (logging)
  private static Map<String, String> fMap;
  
  private static String fWebmaster = ""; //no default
  private static String fImplicitMappingRemoveBasePackage  = ""; //no default
  
  private static String fMailServerConfig = "NONE";
  private static String fMailServerCredentials = "NONE";
  private static String fLoggingDirectory = "NONE";
  private static List<String> fLoggingLevels = Arrays.asList("hirondelle.web4j.level=CONFIG");
  private static List<String> fTroubleTicketMailingList = Collections.emptyList();
  private static Long fMinimumIntervalBetweenTroubleTickets = Long.valueOf(30);
  private static Long fPoorPerformanceThreshold = 20L;
  private static Long fMaxHttpRequestSize = 51200L;
  private static Long fMaxFileUploadRequestSize = 51200L;
  private static Long fMaxRequestParamValueSize = 51200L;
  private static Boolean fSpamDetectionInFirewall = Boolean.FALSE;
  private static Boolean fFullyValidateFileUploads = Boolean.FALSE;
  private static Boolean fAllowStringAsBuildingBlock = Boolean.FALSE;
  private static Map<String, List<String>> fUntrustedProxyForUserId =  new LinkedHashMap<String, List<String>>();
  private static String fCharacterEncoding = "UTF-8";
  private static Locale fDefaultLocale = Util.buildLocale("en");
  private static TimeZone fDefaultUserTimeZone = TimeZone.getTimeZone("GMT");
  private static TimeZone fTimeZoneHint; //default is null in this case
  
  // these 3 are related to each other
  private static String fDecimalSeparator = "PERIOD";
  private static ParseDecimalFormat fDecimalParser = new ParseDecimalFormat();
  private static Pattern fDecimalInputPattern = fDecimalParser.getDecimalFormatPattern(fDecimalSeparator);// the default, as usual
  
  private static String fBigDecimalDisplayFormat = "#,##0.00";
  private static String fBooleanTrueDisplayFormat = "<![CDATA[  <input type='checkbox' name='true' value='true' checked readonly notab> ]]>";
  private static String fBooleanFalseDisplayFormat = "<![CDATA[ <input type='checkbox' name='false' value='false' checked readonly notab>]]>";
  private static String fEmptyOrNullDisplayFormat = "-";
  private static String fIntegerDisplayFormat = "#,###";
  private static String fIgnorableParamValue = "";
  
  //database items
  private static DbConfigParser fIsSQLPrecompilationAttempted = new DbConfigParser("TRUE");
  private static DbConfigParser fMaxRows = new DbConfigParser("300");
  private static DbConfigParser fFetchSize = new DbConfigParser("25");
  private static DbConfigParser fHasAutoGeneratedKeys = new DbConfigParser("FALSE");
  private static DbConfigParser fErrorCodeForDuplicateKey =  new DbConfigParser("1");
  private static DbConfigParser fErrorCodeForForeignKey = new DbConfigParser("2291");
  private static DbConfigParser fSqlFetcherDefaultTxIsolationLevel = new DbConfigParser("DATABASE_DEFAULT");
  private static DbConfigParser fSqlEditorDefaultTxIsolationLevel = new DbConfigParser("DATABASE_DEFAULT");
  private static DbConfigParser fDateTimeFormatForPassingParamsToDb = new DbConfigParser("YYYY-MM-DD^hh:mm:ss^YYYY-MM-DD hh:mm:ss");

  private static Boolean fIsTesting = Boolean.FALSE;
  
  /* Simple conversion methods. */
  
  private static Long asLong(String aValue){
    return Long.valueOf(aValue);
  }
  
  private static Integer asInteger(String aValue){
    return Integer.valueOf(aValue);
  }

  private static TimeZone asTimeZone(String aValue){
    TimeZone result = null;
    try{
      result = Util.buildTimeZone(aValue);
    }
    catch(Throwable ex){
      fErrors.add(ex.getMessage());
    }
    return result;
  }

  private static Locale asLocale(String aValue){
    return Util.buildLocale(aValue);
  }
  
  /** 
   Separated by a comma.
   Special value 'NONE' denotes an empty list. 
  */
  private static List<String> asStringListWithNone(String aValue){
    List<String> result = new ArrayList<String>();
    if (! "NONE".equalsIgnoreCase(aValue)){
      result.addAll(asStringList(aValue));
    }
    return result;
  }
  
  /**  Separated by a comma. */
  private static List<String> asStringList(String aValue){
    List<String> result = new ArrayList<String>();
    StringTokenizer parser = new StringTokenizer(aValue, ",");
    while (parser.hasMoreElements()){
      String item = (String)parser.nextElement();
      result.add(item);
    }
    return result;
  }
  
  
  /** Separated by a comma. */
  private static List<Integer> asIntegerList(String aValue){
    List<Integer> result = new ArrayList<Integer>(); 
    String DELIMITER = ",";
    StringTokenizer parser = new StringTokenizer(aValue, DELIMITER);
    while ( parser.hasMoreTokens() ) {
      String errorCode = parser.nextToken();
      result.add(new Integer(errorCode.trim()));
    }
    return result;
  }
  
  private static Boolean asBoolean(String aValue){
    return Util.parseBoolean(aValue);
  }

  private static String asDateTimeFormat(String aDbSetting, int aPart){
    String[] formats = aDbSetting.split("\\^");
    if(formats.length != 3) {
      fErrors.add(
        "DateTimeFormatForPassingParamsToDb setting in web.xml does not have a valid value: " + Util.quote(aDbSetting) + 
        ". Does not have 3 entries, separated by a '^' character."
      );
    }
    return formats[aPart];
  }
  
  private static void checkTooLow(Long aValue, Long aMin, String aName) {
    if (aValue < aMin){
      fErrors.add(
        "Configured value of " + aValue + " in web.xml for " + aName + 
        " is less than " + aMin  + ". Please see web.xml for more information." 
      );
    }
  }
}
