package hirondelle.web4j;

import hirondelle.web4j.database.ConnectionSource;
import hirondelle.web4j.database.ConvertColumn;
import hirondelle.web4j.database.ConvertColumnImpl;
import hirondelle.web4j.model.AppException;
import hirondelle.web4j.model.ConvertParam;
import hirondelle.web4j.model.ConvertParamError;
import hirondelle.web4j.model.ConvertParamImpl;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelCtorUtil;
import hirondelle.web4j.request.DateConverter;
import hirondelle.web4j.request.LocaleSource;
import hirondelle.web4j.request.LocaleSourceImpl;
import hirondelle.web4j.request.RequestParser;
import hirondelle.web4j.request.RequestParserImpl;
import hirondelle.web4j.request.TimeZoneSource;
import hirondelle.web4j.request.TimeZoneSourceImpl;
import hirondelle.web4j.security.ApplicationFirewall;
import hirondelle.web4j.security.ApplicationFirewallImpl;
import hirondelle.web4j.security.LoginTasks;
import hirondelle.web4j.security.PermittedCharacters;
import hirondelle.web4j.security.PermittedCharactersImpl;
import hirondelle.web4j.security.SpamDetector;
import hirondelle.web4j.security.SpamDetectorImpl;
import hirondelle.web4j.security.UntrustedProxyForUserId;
import hirondelle.web4j.security.UntrustedProxyForUserIdImpl;
import hirondelle.web4j.ui.translate.Translator;
import hirondelle.web4j.util.TimeSource;
import hirondelle.web4j.util.TimeSourceImpl;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.webmaster.Emailer;
import hirondelle.web4j.webmaster.EmailerImpl;
import hirondelle.web4j.webmaster.LoggingConfig;
import hirondelle.web4j.webmaster.LoggingConfigImpl;

import java.lang.reflect.Constructor;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;

/**
 Return concrete instances of configured implementation classes. This is a Service Locator class.
 
 <P>WEB4J requires the application programmer to supply concrete implementations of a number of  
 interfaces and a single abstract class. <tt>BuildImpl</tt> returns instances of those abstractions. Over half 
 of these items have default implementations, which can be used by the application programmer 
 without any configuration effort at all.
 
 <P>When the framework needs a specific implementation, it uses the services of this class. 
 (If the application programmer needs to refer to such an implementation, they have the option of using 
 the methods in this class, instead of referring directly to their implementation.)

 <P><h3>Configuration Styles</h3>
 Concrete implementation classes can be configured in three ways:
 <ul>
 <li>do nothing at all. In this case, a default implementation defined by <tt>WEB4J</tt> will be used. 
 Several of the WEB4J abstractions (such as {@link ConnectionSource}) do 
 not have a default implementation, so this style of configuration is not always possible. 
 <li>implement a concrete class <em>of a conventional package and name</em>. The conventional <em>package</em> name 
 is always '<tt>hirondelle.web4j.config</tt>', while the conventional <em>class</em> name varies - 
 see <a href="#Listing">below</a>. 
 <li>implement a concrete class of a <em>non-conventional</em> package and name, 
 and add an <tt>init-param</tt> setting to <tt>web.xml</tt> of the form:
 <PRE>
  &lt;init-param&gt;
   &lt;param-name&gt;ImplementationFor.hirondelle.web4j.ApplicationInfo&lt;/param-name&gt;
   &lt;param-value&gt;com.xyz.MyAppInfo&lt;/param-value&gt;
   &lt;description&gt;
     Package-qualified name of class describing simple, 
     high level information about this application. 
   &lt;/description&gt;
  &lt;/init-param&gt; 
 </PRE>
 </ul>
 
 <P>The {@link #init(Map)} method will look for implementations in the reverse of the above order. 
 That is, 
<ol>
 <li>an <em>explicit</em> <tt>ImplementationFor.*</tt> setting in <tt>web.xml</tt>
 <li>a class of a <em>conventional</em> package and name
 <li>the <em>default</em> WEB4J implementation (if a default implementation exists)
</ol>
 
 <P><a name="Listing"></a><h3>Listing of Interfaces and Conventional Names</h3>
 Here's a listing of all interfaces used by WEB4J, along with conventional class names, and either a default 
 or example implementation. The package for conventional class names is always '<tt>hirondelle.web4j.config</tt>'.
 
 <P><table BORDER CELLSPACING=0 CELLPADDING=3 >
 <tr>
 <th>Question</th>
 <th>Interface</th>
 <th>Conventional Impl Name, in <tt>hirondelle.web4j.config</tt></th>
 <th>Default/Example Implementation</th>
 </tr>
 <tr valign="top">
 <td>What is the application's name, version, build date, and so on?</td>
 <td>{@link hirondelle.web4j.ApplicationInfo}</td>
 <td><tt>AppInfo</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/AppInfo.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>What tasks need to be performed during startup?</td>
 <td>{@link hirondelle.web4j.StartupTasks}</td>
 <td><tt>Startup</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/Startup.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>What tasks need to be performed after user login?</td>
 <td>{@link hirondelle.web4j.security.LoginTasks}</td>
 <td><tt>Login</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/Login.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>What <tt>Action</tt> is related to each request?</td>
 <td>{@link hirondelle.web4j.request.RequestParser} (an ABC)</td>
 <td><tt>RequestToAction</tt></td>
 <td>{@link hirondelle.web4j.request.RequestParserImpl}</a></td>
 </tr>
 <tr valign="top">
 <td>Which requests should be treated as malicious attacks?</td>
 <td>{@link hirondelle.web4j.security.ApplicationFirewall}</td>
 <td><tt>AppFirewall</tt></td>
 <td>{@link hirondelle.web4j.security.ApplicationFirewallImpl}</td>
 </tr>
 <tr valign="top">
 <td>Which requests use untrusted proxies for the user id?</td>
 <td>{@link hirondelle.web4j.security.UntrustedProxyForUserId}</td>
 <td><tt>OwnerFirewall</tt></td>
 <td>{@link hirondelle.web4j.security.UntrustedProxyForUserIdImpl}</td>
 </tr>
 <tr valign="top">
 <td>How is spam distinguished from regular user input?</td>
 <td>{@link hirondelle.web4j.security.SpamDetector}</td>
 <td><tt>SpamDetect</tt></td>
 <td>{@link hirondelle.web4j.security.SpamDetectorImpl}</td>
 </tr>
 <tr valign="top">
 <td>How is a request param translated into a given target type?</td>
 <td>{@link hirondelle.web4j.model.ConvertParam}</td>
 <td><tt>ConvertParams</tt></td>
 <td>{@link hirondelle.web4j.model.ConvertParamImpl}</td>
 </tr>
 <tr valign="top">
 <td>How does the application respond when a low level conversion error takes place when parsing user input?</td>
 <td>{@link hirondelle.web4j.model.ConvertParamError}</td>
 <td><tt>ConvertParamErrorImpl</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/ConvertParamErrorImpl.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>What characters are permitted for text input fields?</td>
 <td>{@link hirondelle.web4j.security.PermittedCharacters}</td>
 <td><tt>PermittedChars</tt></td>
 <td>{@link hirondelle.web4j.security.PermittedCharactersImpl}</td>
 </tr>
 <tr valign="top">
 <td>How is a date formatted and parsed?</td>
 <td>{@link hirondelle.web4j.request.DateConverter}</td>
 <td><tt>DateConverterImpl</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/DateConverterImpl.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>How is a <tt>Locale</tt> derived from the request?</td>
 <td>{@link hirondelle.web4j.request.LocaleSource}</td>
 <td><tt>LocaleSrc</tt></td>
 <td>{@link hirondelle.web4j.request.LocaleSourceImpl}</td>
 </tr>
 <tr valign="top">
 <td>How is the system clock defined?</td>
 <td>{@link hirondelle.web4j.util.TimeSource}</td>
 <td><tt>TimeSrc</tt></td>
 <td>{@link hirondelle.web4j.util.TimeSourceImpl}</td>
 </tr>
 <tr valign="top">
 <td>How is a <tt>TimeZone</tt> derived from the request?</td>
 <td>{@link hirondelle.web4j.request.TimeZoneSource}</td>
 <td><tt>TimeZoneSrc</tt></td>
 <td>{@link hirondelle.web4j.request.TimeZoneSourceImpl}</td>
 </tr>
 <tr valign="top">
 <td>What is the translation of this text, for a given <tt>Locale</tt>?</td>
 <td>{@link hirondelle.web4j.ui.translate.Translator}</td>
 <td><tt>TranslatorImpl</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/TranslatorImpl.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>How does the application obtain a database <tt>Connection</tt>?</td>
 <td>{@link hirondelle.web4j.database.ConnectionSource}</td>
 <td><tt>ConnectionSrc</tt></td>
 <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/ConnectionSrc.html">example</a></td>
 </tr>
 <tr valign="top">
 <td>How is a <tt>ResultSet</tt> column translated into a given target type?</td>
 <td>{@link hirondelle.web4j.database.ConvertColumn}</td>
 <td><tt>ColToObject</tt></td>
 <td>{@link hirondelle.web4j.database.ConvertColumnImpl}</td>
 </tr>
 <tr valign="top">
 <td>How should an email be sent when a problem occurs?</td>
 <td>{@link hirondelle.web4j.webmaster.Emailer}</td>
<td><tt>Email</tt></td>
 <td>{@link hirondelle.web4j.webmaster.EmailerImpl}</td>
 </tr>
 <tr valign="top">
 <td>How should the logging system be configured?</td>
 <td>{@link hirondelle.web4j.webmaster.LoggingConfig}</td>
 <td><tt>LogConfig</tt></td>
 <td>{@link hirondelle.web4j.webmaster.LoggingConfigImpl}</td>
 </tr>
 <tr valign="top">
 <td>Does this request/operation have a data ownership constraint?</td>
 <td>{@link hirondelle.web4j.security.UntrustedProxyForUserId}</td>
 <td><tt>OwnerFirewall</tt></td>
 <td>{@link hirondelle.web4j.security.UntrustedProxyForUserIdImpl}</td>
 </tr>
 </table>
 
 <p><span class="highlight">No conflict between the classes of different
 applications will result, if application code is placed in the usual locations
 under <tt>WEB-INF</span></tt>, and not in <i>shared</i> locations accessible
 to multiple web applications. Since version 2.2 of the Servlet API, each
 web application gets its own {@link java.lang.ClassLoader}, so no conflict
 will result, as long as classes are placed in non-shared locations (which
 is almost always the case).

 <P>This class does not cache objects in any way.
*/
public final class BuildImpl {
  
  /*
   Note: some might make better use of generics here. BUT from the point of view of the 
   caller, the forXXX methods should remain. That way the caller does not have to 
   remember the interface class literal. (As well, the config settings for 
   intf/impl are text, not class literals.)
  */
  
  /**
   Called by the framework upon startup. 
   
   <P>Extract all configuration which maps names of abstractions to names of corresponding  
   concrete implementations. Confirm both that all required interfaces 
   have configured implementations, and that they can be loaded.
   
   <P>The implementation of {@link TimeSource} and {@link LoggingConfig} are treated slightly 
   differently than the rest. Their implementations are found and used earlier than the others, since they 
   are of immediate use. 
   
   <P>See class comment for more information.
   @param aConfig contains information regarding custom implementations, and information
   for logging config (if any); in a servlet context, this information is extracted by the framework
   from settings in <tt>web.xml</tt>.
  */
  public static void init(Map<String, String> aConfig) throws AppException {
    useConfigSettingsFirst(aConfig);
    doTimeSourceConfig();
    doLoggingConfig(aConfig);
    fLogger.config("________________________ STARTUP :Initializing WEB4J Controller. Reading in settings in web.xml._________");
    useStandardOrDefaultNameSecond();
    fLogger.config("Mapping of implementation classes : " + Util.logOnePerLine(fClassMapping));
  }
  
  /**
   Intended for contexts outside of normal servlet operation, where the caller wants to use the 
   web4j database and model layer only. (For example, a command-line application.)
   This method uses these interfaces, in the stated order:
   <ul>
     <li>{@link TimeSource} 
     <li>{@link LoggingConfig}
     <li>{@link ConnectionSource}
     <li>{@link ConvertColumn}
     <li>{@link ConvertParam} (for the list of supported types)
     <li>{@link PermittedCharacters} (used by the <tt>Id</tt> class in the model objects returned by the database)
     <li>{@link DateConverter} (if supplied in the config; used when generating formatted reports)
     <li>{@link SpamDetector} (used when checking input for spam)
   </ul> 
   Usually, the caller will need to supply only one of the above - <tt>ConnectionSource</tt>.
   For the other items, the default implementations will usually be adequate, and no action is required.
  */
  public static void initDatabaseLayer(Map<String, String> aConfig) throws AppException {
    useConfigSettingsFirst(aConfig);
    //two items done early
    doTimeSourceConfig();
    doLoggingConfig(aConfig);
    fLogger.config("For items *not* specified in config, searching for implementations with 'standard' name.");
    fLogger.config("If no 'standard' implementation found, then will use the WEB4J 'default' implementation.");
    //does NOT include the items done 'early'
    addStandardDefaultIfNotInConfig(CONNECTION_SOURCE);
    addStandardDefaultIfNotInConfig(CONVERT_COLUMN);
    addStandardDefaultIfNotInConfig(CONVERT_PARAM); //for supported types
    addStandardDefaultIfNotInConfig(PERMITTED_CHARACTERS); //for Id class
    addStandardDefaultIfNotInConfig(DATE_CONVERTER); //for reports; there's no default impl for this
    addStandardDefaultIfNotInConfig(SPAM_DETECTOR); //for Checking spam
    fLogger.config("Mapping of implementation classes : " + Util.logOnePerLine(fClassMapping));
  }

  /**
   Map a fully-qualified <tt>aAbstractionName</tt> into a concrete implementation.
  
   <P>This method should only be used for 'non-standard' items not covered by more  
   specific methods in this class. For example, when looking for the implementation of {@link LocaleSource},
   the {@link #forLocaleSource()} method should always be used instead of this method.
   
   <P>Implementation classes accessed by this method must have a <tt>public</tt> no-argument 
   constructor. (This method is best suited for interfaces, and not abstract base classes.)
   
   <P>Uses {@link Class#newInstance}, with no arguments. If a problem occurs, a 
   {@link RuntimeException} is thrown. 
  
   @param aAbstractionName package-qualified name of an interface or abstract base class, as in 
   "<tt>hirondelle.web4j.ApplicationInfo</tt>".
  */
  public static Object forAbstraction(String aAbstractionName){
    Class<?> implementationClass = fClassMapping.get(aAbstractionName);
    if ( implementationClass == null )  {
      throw new IllegalArgumentException(
        "No mapping to an implementation class found, for interface or abstract base class named " + Util.quote(aAbstractionName)
      );
    }
    Object result = null;
    try {
      result = implementationClass.newInstance();
    }
    catch (InstantiationException ex){
      handleCtorProblem(ex, implementationClass);
    }
    catch (IllegalAccessException ex) {
      handleCtorProblem(ex, implementationClass);
    }
    return result;
  }
  
  /**
   Map a fully-qualified <tt>aAbstractBaseClassName</tt> into a concrete implementation.
  
   <P>Intended for abstract base classes (ABC's) having a <tt>public</tt> 
   constructor with known arguments. For example, this method is used by 
   the {@link hirondelle.web4j.Controller} to build an implementation of 
   {@link hirondelle.web4j.request.RequestParser}, by passing in a <tt>request</tt> 
   and <tt>response</tt> object. (Implementations of that ABC are always expected to 
   take those two particular constructor arguments.)
   
   <P>If a problem occurs, a {@link RuntimeException} is thrown.
   
   @param aAbstractBaseClassName package-qualified name of an Abstract Base Class, as in 
   "<tt>hirondelle.web4j.ui.RequestParser</tt>".
   @param aCtorArguments <tt>List</tt> of arguments to be passed to the constructor of an  
   implementation class; the size of this list determines the selected constructor (by 
   matching the number of parameters), and the iteration order of its items corresponds 
   to the order of appearance of the formal constructor parameters.
  */
  public static Object forAbstractionPassCtorArgs(String aAbstractBaseClassName, List<Object> aCtorArguments){
    Object result = null;
    Class implClass = fClassMapping.get(aAbstractBaseClassName);
    Constructor ctor = ModelCtorUtil.getConstructor(implClass, aCtorArguments.size());
    try {
      result = ModelCtorUtil.buildModelObject(ctor, aCtorArguments);
    }
    catch (ModelCtorException ex){
      handleCtorProblem(ex, implClass);
    }
    return result;
  }
  
  /** Return the configured implementation of {@link ApplicationInfo}.  */
  public static ApplicationInfo forApplicationInfo(){
    return (ApplicationInfo)forAbstraction(APPLICATION_INFO.getAbstraction());
  }
  
  /** Return the configured implementation of {@link StartupTasks}.  */
  public static StartupTasks forStartupTasks(){
    return (StartupTasks)forAbstraction(STARTUP_TASKS.getAbstraction());
  }
  
  /** Return the configured implementation of {@link LoginTasks}.  */
  public static LoginTasks forLoginTasks(){
    return (LoginTasks)forAbstraction(LOGIN_TASKS.getAbstraction());
  }
  
  /** Return the configured implementation of {@link ConvertParamError}.  */
  public static ConvertParamError forConvertParamError(){
    return (ConvertParamError)forAbstraction(CONVERT_PARAM_ERROR.getAbstraction());
  }
  
  /** Return the configured implementation of {@link ConvertColumn}.  */
  public static ConvertColumn forConvertColumn(){
    return (ConvertColumn)forAbstraction(CONVERT_COLUMN.getAbstraction());
  }

  /** Return the configured implementation of {@link PermittedCharacters}.  */
  public static PermittedCharacters forPermittedCharacters(){
    return (PermittedCharacters)forAbstraction(PERMITTED_CHARACTERS.getAbstraction());
  }
  
  /** Return the configured implementation of {@link ConnectionSource}.  */
  public static ConnectionSource forConnectionSource(){
    return (ConnectionSource)forAbstraction(CONNECTION_SOURCE.getAbstraction());    
  }
  
  /** Return the configured implementation of {@link LocaleSource}.  */
  public static LocaleSource forLocaleSource(){
    return (LocaleSource)forAbstraction(LOCALE_SRC.getAbstraction());
  }

  /** 
  Return the configured implementation of {@link TimeSource}.
  
  <P>When testing, an application may call this method in order to use a 'fake'
  system time.
  
   <P>Internally, WEB4J will always use this method when it needs the current time.
   This allows a fake system time to be shared between your application and WEB4J.
  */
  public static TimeSource forTimeSource(){
    return (TimeSource)forAbstraction(TIME_SRC.getAbstraction());
  }
  
  /** Return the configured implementation of {@link TimeZoneSource}.  */
  public static TimeZoneSource forTimeZoneSource(){
    return (TimeZoneSource)forAbstraction(TIME_ZONE_SRC.getAbstraction());
  }
  
  /** Return the configured implementation of {@link DateConverter}.  */
  public static DateConverter forDateConverter(){
    return (DateConverter)forAbstraction(DATE_CONVERTER.getAbstraction());
  }
  
  /** Return the configured implementation of {@link Translator}.  */
  public static Translator forTranslator(){
    return (Translator)forAbstraction(TRANSLATOR.getAbstraction());
  }

  /** Return the configured implementation of {@link ApplicationFirewall}.  */
  public static ApplicationFirewall forApplicationFirewall() {
    return (ApplicationFirewall)forAbstraction(APP_FIREWALL.getAbstraction());
  }
  
  /** Return the configured implementation of {@link SpamDetector}.  */
  public static SpamDetector forSpamDetector() {
    return (SpamDetector)forAbstraction(SPAM_DETECTOR.getAbstraction());
  }
  
  /** Return the configured implementation of {@link Emailer}.  */
  public static Emailer forEmailer() {
    return (Emailer)forAbstraction(EMAILER.getAbstraction());
  }
  
  /** Return the configured implementation of {@link ConvertParam}.  */
  public static ConvertParam forConvertParam() {
    return (ConvertParam)forAbstraction(CONVERT_PARAM.getAbstraction());
  }
  
  /** Return the configured implementation of {@link UntrustedProxyForUserId}.  */
  public static UntrustedProxyForUserId forOwnershipFirewall() {
    return (UntrustedProxyForUserId)forAbstraction(OWNER_FIREWALL.getAbstraction());
  }
  
  /**
    Add an implementation - intended for testing only.
      
     <P>This method allows testing code to configure a specific implementation class. 
     Example: <PRE>BuildImpl.adHocImplementationAdd(TimeSource.class, MyTimeSource.class);</PRE>
     Calls to this method (often in a JUnit <tt>setUp()</tt> method) should be paired with a 
     subsequent call to {@link #adHocImplementationRemove(Class)}. 
  */
  public static void adHocImplementationAdd(Class aInterface, Class aImplementationClass){
     fClassMapping.put(aInterface.getName(), aImplementationClass);
  }
  
  /**
    Remove an implementation - intended for testing only.
     
    <P>This method allows testing code to configure a specific implementation class. 
    Example: <PRE>BuildImpl.adHocImplementationRemove(TimeSource.class);</PRE> 
    Calls to this method (often in a JUnit <tt>tearDown()</tt> method) should be paired with 
    a previous call to {@link #adHocImplementationAdd(Class, Class)}. 
  */
  public static void adHocImplementationRemove(Class aInterface){
    fClassMapping.remove(aInterface.getName());
  }
  
  // PRIVATE 
  
  /**
   Key - interface name (String)
   Value - implementation class (Class) 
  */
  private static final Map<String, Class<?>> fClassMapping = new LinkedHashMap<String, Class<?>>();
  
  private static final String IMPLEMENTATION_FOR = "ImplementationFor.";
  private static final Logger fLogger = Util.getLogger(BuildImpl.class);
  
  private BuildImpl() {
    //prevent construction by caller
  }
  
  /*
   Implementation Note. 
   Early versions of this class did not work with class literals. Problem disappeared?
  */
  
  private static final String STANDARD_PACKAGE = "hirondelle.web4j.config.";
  
  //Items with no WEB4J default
  private static final StandardDefault APPLICATION_INFO = new StandardDefault(ApplicationInfo.class.getName(),"AppInfo");
  private static final StandardDefault STARTUP_TASKS = new StandardDefault(StartupTasks.class.getName(), "Startup");
  private static final StandardDefault LOGIN_TASKS = new StandardDefault(LoginTasks.class.getName(), "Login");
  private static final StandardDefault CONNECTION_SOURCE = new StandardDefault(ConnectionSource.class.getName(), "ConnectionSrc");
  private static final StandardDefault CONVERT_PARAM_ERROR = new StandardDefault(ConvertParamError.class.getName(), "ConvertParamErrorImpl");
  private static final StandardDefault TRANSLATOR = new StandardDefault(Translator.class.getName(), "TranslatorImpl");
  private static final StandardDefault DATE_CONVERTER = new StandardDefault(DateConverter.class.getName(), "DateConverterImpl");
  
  //Items with a WEB4J default
  private static final StandardDefault LOGGING_CONFIG = new StandardDefault(LoggingConfig.class.getName(), "LogConfig", LoggingConfigImpl.class.getName());
  private static final StandardDefault REQUEST_PARSER = new StandardDefault(RequestParser.class.getName(), "RequestToAction", RequestParserImpl.class.getName());
  private static final StandardDefault APP_FIREWALL = new StandardDefault(ApplicationFirewall.class.getName(), "AppFirewall", ApplicationFirewallImpl.class.getName());
  private static final StandardDefault CONVERT_COLUMN = new StandardDefault(ConvertColumn.class.getName(), "ConvertColumns", ConvertColumnImpl.class.getName());
  private static final StandardDefault LOCALE_SRC = new StandardDefault(LocaleSource.class.getName(), "LocaleSrc", LocaleSourceImpl.class.getName());
  private static final StandardDefault TIME_SRC = new StandardDefault(TimeSource.class.getName(), "TimeSrc", TimeSourceImpl.class.getName());
  private static final StandardDefault TIME_ZONE_SRC = new StandardDefault(TimeZoneSource.class.getName(), "TimeZoneSrc", TimeZoneSourceImpl.class.getName());
  private static final StandardDefault SPAM_DETECTOR = new StandardDefault(SpamDetector.class.getName(), "SpamDetect", SpamDetectorImpl.class.getName());
  private static final StandardDefault EMAILER = new StandardDefault(Emailer.class.getName(), "Email", EmailerImpl.class.getName());
  private static final StandardDefault CONVERT_PARAM = new StandardDefault(ConvertParam.class.getName(), "ConvertParams", ConvertParamImpl.class.getName());
  private static final StandardDefault PERMITTED_CHARACTERS = new StandardDefault(PermittedCharacters.class.getName(), "PermittedChars", PermittedCharactersImpl.class.getName());
  private static final StandardDefault OWNER_FIREWALL = new StandardDefault(UntrustedProxyForUserId.class.getName(), "OwnerFirewall", UntrustedProxyForUserIdImpl.class.getName());
  
  //OTHERS? MUST add below as well...

  private static void useConfigSettingsFirst(Map<String, String> aConfig) throws AppException {
    for(String name : aConfig.keySet()){
      if ( name.startsWith(IMPLEMENTATION_FOR) ) {
        String interfaceName = name.substring(IMPLEMENTATION_FOR.length());
        String className = aConfig.get(name);
        fClassMapping.put(interfaceName, buildClassFromConfig(className));
      }
    }
  }
  
  private static void handleCtorProblem(Exception ex, Class<?> aImplementationClass){
    String message = "Object construction by reflection failed for " + aImplementationClass.toString();
    fLogger.severe(message);
    throw new RuntimeException(message, ex);
  }
  
  /** The system time must be done first, since used everywhere, including the logging system. */
  private static void doTimeSourceConfig() throws AppException {
    addStandardDefaultIfNotInConfig(TIME_SRC); 
  }

  /**  Return the configured implementation of {@link LoggingConfig}.  */ 
  private static LoggingConfig forLoggingConfig(){
    return (LoggingConfig)forAbstraction(LOGGING_CONFIG.getAbstraction());
  }
  
  /** Extract and execute the LoggingConfig, earlier than all others.  */
  private static void doLoggingConfig(Map<String, String> aConfig) throws AppException {
    addStandardDefaultIfNotInConfig(LOGGING_CONFIG); 
    executeLoggingConfig(aConfig);
  }
  
  private static void useStandardOrDefaultNameSecond() throws AppException {
    fLogger.config("For items *not* specified in config, searching for implementations with 'standard' name.");
    fLogger.config("If no 'standard' implementation found, then will use the WEB4J 'default' implementation.");
    //does NOT include the items done 'early'
    addStandardDefaultIfNotInConfig(APPLICATION_INFO );
    addStandardDefaultIfNotInConfig(CONNECTION_SOURCE );
    addStandardDefaultIfNotInConfig(CONVERT_PARAM_ERROR );
    addStandardDefaultIfNotInConfig(TRANSLATOR );
    addStandardDefaultIfNotInConfig(DATE_CONVERTER );
    addStandardDefaultIfNotInConfig(STARTUP_TASKS );
    addStandardDefaultIfNotInConfig(LOGIN_TASKS );
    addStandardDefaultIfNotInConfig(REQUEST_PARSER );
    addStandardDefaultIfNotInConfig(APP_FIREWALL );
    addStandardDefaultIfNotInConfig(CONVERT_COLUMN );
    addStandardDefaultIfNotInConfig(LOCALE_SRC );
    addStandardDefaultIfNotInConfig(TIME_ZONE_SRC );
    addStandardDefaultIfNotInConfig(SPAM_DETECTOR );
    addStandardDefaultIfNotInConfig(EMAILER );
    addStandardDefaultIfNotInConfig(CONVERT_PARAM );
    addStandardDefaultIfNotInConfig(PERMITTED_CHARACTERS );
    addStandardDefaultIfNotInConfig(OWNER_FIREWALL );
  }
  
  private static boolean isAlreadySpecified(String aInterfaceName){
    return fClassMapping.keySet().contains(aInterfaceName);
  }

  private static Class<?> buildClassFromConfig(String aClassName) throws AppException {
    Class<?> result = null;
    try {
      result = Class.forName(aClassName);
    }
    catch (ClassNotFoundException ex){
      throw new AppException(
        "Load of configured (or default) implementation class has failed. Class.forName() failed for " + Util.quote(aClassName), ex
      );
    }
    return result;
  }

  private static void addStandardDefaultIfNotInConfig(StandardDefault aNames) throws AppException {
    if ( ! isAlreadySpecified(aNames.getAbstraction()) ){
      fClassMapping.put(aNames.getAbstraction(), buildStandardOrDefaultClass(aNames.getStandard(), aNames.getDefault()));
    }
  }
  
  private static Class<?> buildStandardOrDefaultClass(String aStandardName, String aDefaultName) throws AppException {
    Class<?> result = null;
    try {
      result = Class.forName(aStandardName);
    }
    catch (ClassNotFoundException ex){
      if( Util.textHasContent(aDefaultName) ){
        fLogger.config("Cannot see any class with standard name " + Util.quote(aStandardName) + ". Will use default WEB4J implementation instead, named " + Util.quote(aDefaultName));
        result = useDefault(aDefaultName);
      }
      else {
        reportMissing(aStandardName, ex);
      }
    }
    return result;
  }

  private static Class<?> useDefault(String aDefaultName) throws AppException {
    Class<?> result = null;
    try {
      result = Class.forName(aDefaultName);
    }
    catch (ClassNotFoundException exception){
      throw new AppException(
        "Load of default implementation has failed. Class.forName() failed for " + Util.quote(aDefaultName), exception
      );
    }
    return result;
  }

  private static void reportMissing(String aStandardName, ClassNotFoundException ex) throws AppException {
    String msg = "Load of configured implementation class has failed. Class.forName() failed for " + Util.quote(aStandardName);
    throw new AppException(msg, ex);
  }

  /** Execute the configured implementation of {@link LoggingConfig}.  */
  private static void executeLoggingConfig(Map<String, String> aConfig) throws AppException {
    LoggingConfig loggingConfig = forLoggingConfig();
    loggingConfig.setup(aConfig);
  }
  
  /** Gathers the standard and default class names related to an abstraction.   */
  private static final class StandardDefault {
    StandardDefault(String aAbstraction, String aStandard){
      fAbstraction = aAbstraction;
      fStandard = STANDARD_PACKAGE + aStandard;
    }
    StandardDefault(String aAbstraction, String aStandard, String aDefault){
      fAbstraction = aAbstraction;
      fStandard = STANDARD_PACKAGE + aStandard;
      fDefault = aDefault;
    }
    String getAbstraction() {  return fAbstraction;    }
    String getDefault() {   return fDefault;   }
    String getStandard() {    return fStandard;   }
    private String fAbstraction;
    private String fStandard;
    private String fDefault;
  }
}
