001    package hirondelle.web4j;
002    
003    import hirondelle.web4j.database.ConnectionSource;
004    import hirondelle.web4j.database.ConvertColumn;
005    import hirondelle.web4j.database.ConvertColumnImpl;
006    import hirondelle.web4j.model.AppException;
007    import hirondelle.web4j.model.ConvertParam;
008    import hirondelle.web4j.model.ConvertParamError;
009    import hirondelle.web4j.model.ConvertParamImpl;
010    import hirondelle.web4j.model.ModelCtorException;
011    import hirondelle.web4j.model.ModelCtorUtil;
012    import hirondelle.web4j.request.DateConverter;
013    import hirondelle.web4j.request.LocaleSource;
014    import hirondelle.web4j.request.LocaleSourceImpl;
015    import hirondelle.web4j.request.RequestParser;
016    import hirondelle.web4j.request.RequestParserImpl;
017    import hirondelle.web4j.request.TimeZoneSource;
018    import hirondelle.web4j.request.TimeZoneSourceImpl;
019    import hirondelle.web4j.security.ApplicationFirewall;
020    import hirondelle.web4j.security.ApplicationFirewallImpl;
021    import hirondelle.web4j.security.LoginTasks;
022    import hirondelle.web4j.security.PermittedCharacters;
023    import hirondelle.web4j.security.PermittedCharactersImpl;
024    import hirondelle.web4j.security.SpamDetector;
025    import hirondelle.web4j.security.SpamDetectorImpl;
026    import hirondelle.web4j.security.UntrustedProxyForUserId;
027    import hirondelle.web4j.security.UntrustedProxyForUserIdImpl;
028    import hirondelle.web4j.ui.translate.Translator;
029    import hirondelle.web4j.util.TimeSource;
030    import hirondelle.web4j.util.TimeSourceImpl;
031    import hirondelle.web4j.util.Util;
032    import hirondelle.web4j.webmaster.Emailer;
033    import hirondelle.web4j.webmaster.EmailerImpl;
034    import hirondelle.web4j.webmaster.LoggingConfig;
035    import hirondelle.web4j.webmaster.LoggingConfigImpl;
036    
037    import java.lang.reflect.Constructor;
038    import java.util.Enumeration;
039    import java.util.LinkedHashMap;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.logging.Logger;
043    
044    import javax.servlet.ServletConfig;
045    
046    /**
047     Return concrete instances of configured implementation classes.
048     
049     <P>WEB4J requires the application programmer to supply concrete implementations of a number of  
050     interfaces and a single abstract class. <tt>BuildImpl</tt> returns instances of those abstractions. Over half 
051     of these items have default implementations, which can be used by the application programmer 
052     without any configuration effort at all.
053     
054     <P>When the framework needs a specific implementation, it uses the services of this class. 
055     (If the application programmer needs to refer to such an implementation, they have the option of using 
056     the methods in this class, instead of referring directly to their implementation.)
057    
058     <P><h3>Configuration Styles</h3>
059     Concrete implementation classes can be configured in three ways :
060     <ul>
061     <li>do nothing at all. In this case, a default implementation defined by <tt>WEB4J</tt> will be used. 
062     Several of the WEB4J abstractions (such as {@link ConnectionSource}) do 
063     not have a default implementation, so this style of configuration is not always possible. 
064     <li>implement a concrete class <em>of a conventional package and name</em>. The conventional <em>package</em> name 
065     is always '<tt>hirondelle.web4j.config</tt>', while the conventional <em>class</em> name varies - 
066     see <a href="#Listing">below</a>. 
067     <li>implement a concrete class of a <em>non-conventional</em> package and name, 
068     and add an <tt>init-param</tt> setting to <tt>web.xml</tt> of the form :
069     <PRE>
070      &lt;init-param&gt;
071       &lt;param-name&gt;ImplementationFor.hirondelle.web4j.ApplicationInfo&lt;/param-name&gt;
072       &lt;param-value&gt;com.xyz.MyAppInfo&lt;/param-value&gt;
073       &lt;description&gt;
074         Package-qualified name of class describing simple, 
075         high level information about this application. 
076       &lt;/description&gt;
077      &lt;/init-param&gt; 
078     </PRE>
079     </ul>
080     
081     <P>The {@link #init(ServletConfig)} method will look for implementations in the reverse of the above order. 
082     That is, 
083    <ol>
084     <li>an <em>explicit</em> <tt>ImplementationFor.*</tt> setting in <tt>web.xml</tt>
085     <li>a class of a <em>conventional</em> package and name
086     <li>the <em>default</em> WEB4J implementation (if a default implementation exists)
087    </ol>
088     
089     <P><a name="Listing"></a><h3>Listing of Interfaces and Conventional Names</h3>
090     Here is a listing of all interfaces used by WEB4J, along with conventional class names, and either a default 
091     or example implementation. The package for conventional class names is always '<tt>hirondelle.web4j.config</tt>'.
092     
093     <P><table BORDER CELLSPACING=0 CELLPADDING=3 >
094     <tr>
095     <th>Question</th>
096     <th>Interface</th>
097     <th>Conventional Impl Name, in <tt>hirondelle.web4j.config</tt></th>
098     <th>Default/Example Implementation</th>
099     </tr>
100     <tr valign="top">
101     <td>What is the application's name, version, build date, and so on?</td>
102     <td>{@link hirondelle.web4j.ApplicationInfo}</td>
103     <td><tt>AppInfo</tt></td>
104     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/AppInfo.html">example</a></td>
105     </tr>
106     <tr valign="top">
107     <td>What tasks need to be performed during startup?</td>
108     <td>{@link hirondelle.web4j.StartupTasks}</td>
109     <td><tt>Startup</tt></td>
110     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/Startup.html">example</a></td>
111     </tr>
112     <tr valign="top">
113     <td>What tasks need to be performed after user login?</td>
114     <td>{@link hirondelle.web4j.security.LoginTasks}</td>
115     <td><tt>Login</tt></td>
116     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/Login.html">example</a></td>
117     </tr>
118     <tr valign="top">
119     <td>What <tt>Action</tt> is related to each request?</td>
120     <td>{@link hirondelle.web4j.request.RequestParser} (an ABC)</td>
121     <td><tt>RequestToAction</tt></td>
122     <td>{@link hirondelle.web4j.request.RequestParserImpl}</a></td>
123     </tr>
124     <tr valign="top">
125     <td>Which requests should be treated as malicious attacks?</td>
126     <td>{@link hirondelle.web4j.security.ApplicationFirewall}</td>
127     <td><tt>AppFirewall</tt></td>
128     <td>{@link hirondelle.web4j.security.ApplicationFirewallImpl}</td>
129     </tr>
130     <tr valign="top">
131     <td>Which requests use untrusted proxies for the user id?</td>
132     <td>{@link hirondelle.web4j.security.UntrustedProxyForUserId}</td>
133     <td><tt>OwnerFirewall</tt></td>
134     <td>{@link hirondelle.web4j.security.UntrustedProxyForUserIdImpl}</td>
135     </tr>
136     <tr valign="top">
137     <td>How is spam distinguished from regular user input?</td>
138     <td>{@link hirondelle.web4j.security.SpamDetector}</td>
139     <td><tt>SpamDetect</tt></td>
140     <td>{@link hirondelle.web4j.security.SpamDetectorImpl}</td>
141     </tr>
142     <tr valign="top">
143     <td>How is a request param translated into a given target type?</td>
144     <td>{@link hirondelle.web4j.model.ConvertParam}</td>
145     <td><tt>ConvertParams</tt></td>
146     <td>{@link hirondelle.web4j.model.ConvertParamImpl}</td>
147     </tr>
148     <tr valign="top">
149     <td>How does the application respond when a low level conversion error takes place when parsing user input?</td>
150     <td>{@link hirondelle.web4j.model.ConvertParamError}</td>
151     <td><tt>ConvertParamErrorImpl</tt></td>
152     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/ConvertParamErrorImpl.html">example</a></td>
153     </tr>
154     <tr valign="top">
155     <td>What characters are permitted for text input fields?</td>
156     <td>{@link hirondelle.web4j.security.PermittedCharacters}</td>
157     <td><tt>PermittedChars</tt></td>
158     <td>{@link hirondelle.web4j.security.PermittedCharactersImpl}</td>
159     </tr>
160     <tr valign="top">
161     <td>How is a date formatted and parsed?</td>
162     <td>{@link hirondelle.web4j.request.DateConverter}</td>
163     <td><tt>DateConverterImpl</tt></td>
164     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/DateConverterImpl.html">example</a></td>
165     </tr>
166     <tr valign="top">
167     <td>How is a <tt>Locale</tt> derived from the request?</td>
168     <td>{@link hirondelle.web4j.request.LocaleSource}</td>
169     <td><tt>LocaleSrc</tt></td>
170     <td>{@link hirondelle.web4j.request.LocaleSourceImpl}</td>
171     </tr>
172     <tr valign="top">
173     <td>How is the system clock defined?</td>
174     <td>{@link hirondelle.web4j.util.TimeSource}</td>
175     <td><tt>TimeSrc</tt></td>
176     <td>{@link hirondelle.web4j.util.TimeSourceImpl}</td>
177     </tr>
178     <tr valign="top">
179     <td>How is a <tt>TimeZone</tt> derived from the request?</td>
180     <td>{@link hirondelle.web4j.request.TimeZoneSource}</td>
181     <td><tt>TimeZoneSrc</tt></td>
182     <td>{@link hirondelle.web4j.request.TimeZoneSourceImpl}</td>
183     </tr>
184     <tr valign="top">
185     <td>What is the translation of this text, for a given <tt>Locale</tt>?</td>
186     <td>{@link hirondelle.web4j.ui.translate.Translator}</td>
187     <td><tt>TranslatorImpl</tt></td>
188     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/TranslatorImpl.html">example</a></td>
189     </tr>
190     <tr valign="top">
191     <td>How does the application obtain a database <tt>Connection</tt>?</td>
192     <td>{@link hirondelle.web4j.database.ConnectionSource}</td>
193     <td><tt>ConnectionSrc</tt></td>
194     <td><a href="http://www.javapractices.com/apps/fish/javadoc/src-html/hirondelle/web4j/config/ConnectionSrc.html">example</a></td>
195     </tr>
196     <tr valign="top">
197     <td>How is a <tt>ResultSet</tt> column translated into a given target type?</td>
198     <td>{@link hirondelle.web4j.database.ConvertColumn}</td>
199     <td><tt>ColToObject</tt></td>
200     <td>{@link hirondelle.web4j.database.ConvertColumnImpl}</td>
201     </tr>
202     <tr valign="top">
203     <td>How should an email be sent when a problem occurs?</td>
204     <td>{@link hirondelle.web4j.webmaster.Emailer}</td>
205    <td><tt>Email</tt></td>
206     <td>{@link hirondelle.web4j.webmaster.EmailerImpl}</td>
207     </tr>
208     <tr valign="top">
209     <td>How should the logging system be configured?</td>
210     <td>{@link hirondelle.web4j.webmaster.LoggingConfig}</td>
211     <td><tt>LogConfig</tt></td>
212     <td>{@link hirondelle.web4j.webmaster.LoggingConfigImpl}</td>
213     </tr>
214     <tr valign="top">
215     <td>Does this request/operation have a data ownership constraint?</td>
216     <td>{@link hirondelle.web4j.security.UntrustedProxyForUserId}</td>
217     <td><tt>OwnerFirewall</tt></td>
218     <td>{@link hirondelle.web4j.security.UntrustedProxyForUserIdImpl}</td>
219     </tr>
220     </table>
221     
222     <p><span class="highlight">No conflict between the classes of different
223     applications will result, if application code is placed in the usual locations
224     under <tt>WEB-INF</span></tt>, and not in <i>shared</i> locations accessible
225     to multiple web applications. Since version 2.2 of the Servlet API, each
226     web application gets its own {@link java.lang.ClassLoader}, so no conflict
227     will result, as long as classes are placed in non-shared locations (which
228     is almost always the case).
229    
230     <P>This class does not cache objects in any way.
231    */
232    public final class BuildImpl {
233      
234      /*
235       Note: some might make better use of generics here. BUT from the point of view of the 
236       caller, the forXXX methods should remain. That way the caller does not have to 
237       remember the interface class literal. (As well, the web.xml settings for 
238       intf/impl are text, not class literals.)
239      */
240      
241      /**
242       Called by the framework upon startup. 
243       
244       <P>Extract all configuration which maps names of abstractions to names of corresponding  
245       concrete implementations. Confirm both that all required interfaces 
246       have configured implementations, and that they can be loaded.
247       
248       <P>The implementation of {@link TimeSource} and {@link LoggingConfig} are treated slightly 
249       differently than the rest. Their implementations are found earlier than the others, since they 
250       are of immediate use. 
251       
252       <P>See class comment for more information.
253      */
254      public static void init(ServletConfig aConfig) throws AppException {
255        useWebXmlSettingsFirst(aConfig);
256        doTimeSourceConfig();
257        doLoggingConfig(aConfig);
258        fLogger.config("________________________ STARTUP :Initializing WEB4J Controller. Reading in settings in web.xml._________");
259        useStandardOrDefaultNameSecond();
260        fLogger.config("Mapping of implementation classes : " + Util.logOnePerLine(fClassMapping));
261      }
262    
263      /**
264       Map a fully-qualified <tt>aAbstractionName</tt> into a concrete implementation.
265      
266       <P>This method should only be used for 'non-standard' items not covered by more  
267       specific methods in this class. For example, when looking for the implementation of {@link LocaleSource},
268       the {@link #forLocaleSource()} method should always be used instead of this method.
269       
270       <P>Implementation classes accessed by this method must have a <tt>public</tt> no-argument 
271       constructor. (This method is best suited for interfaces, and not abstract base classes.)
272       
273       <P>Uses {@link Class#newInstance}, with no arguments. If a problem occurs, a 
274       {@link RuntimeException} is thrown. 
275      
276       @param aAbstractionName package-qualified name of an interface or abstract base class, as in 
277       "<tt>hirondelle.web4j.ApplicationInfo</tt>".
278      */
279      public static Object forAbstraction(String aAbstractionName){
280        Class<?> implementationClass = fClassMapping.get(aAbstractionName);
281        if ( implementationClass == null )  {
282          throw new IllegalArgumentException(
283            "No mapping to an implementation class found, for interface or abstract base class named " + Util.quote(aAbstractionName)
284          );
285        }
286        Object result = null;
287        try {
288          result = implementationClass.newInstance();
289        }
290        catch (InstantiationException ex){
291          handleCtorProblem(ex, implementationClass);
292        }
293        catch (IllegalAccessException ex) {
294          handleCtorProblem(ex, implementationClass);
295        }
296        return result;
297      }
298      
299      /**
300       Map a fully-qualified <tt>aAbstractBaseClassName</tt> into a concrete implementation.
301      
302       <P>Intended for abstract base classes (ABC's) having a <tt>public</tt> 
303       constructor with known arguments. For example, this method is used by 
304       the {@link hirondelle.web4j.Controller} to build an implementation of 
305       {@link hirondelle.web4j.request.RequestParser}, by passing in a <tt>request</tt> 
306       and <tt>response</tt> object. (Implementations of that ABC are always expected to 
307       take those two particular constructor arguments.)
308       
309       <P>If a problem occurs, a {@link RuntimeException} is thrown.
310       
311       @param aAbstractBaseClassName package-qualified name of an Abstract Base Class, as in 
312       "<tt>hirondelle.web4j.ui.RequestParser</tt>".
313       @param aCtorArguments <tt>List</tt> of arguments to be passed to the constructor of an  
314       implementation class; the size of this list determines the selected constructor (by 
315       matching the number of parameters), and the iteration order of its items corresponds 
316       to the order of appearance of the formal constructor parameters.
317      */
318      public static Object forAbstractionPassCtorArgs(String aAbstractBaseClassName, List<Object> aCtorArguments){
319        Object result = null;
320        Class implClass = fClassMapping.get(aAbstractBaseClassName);
321        Constructor ctor = ModelCtorUtil.getConstructor(implClass, aCtorArguments.size());
322        try {
323          result = ModelCtorUtil.buildModelObject(ctor, aCtorArguments);
324        }
325        catch (ModelCtorException ex){
326          handleCtorProblem(ex, implClass);
327        }
328        return result;
329      }
330      
331      /** Return the configured implementation of {@link ApplicationInfo}.  */
332      public static ApplicationInfo forApplicationInfo(){
333        return (ApplicationInfo)forAbstraction(APPLICATION_INFO.getAbstraction());
334      }
335      
336      /** Return the configured implementation of {@link StartupTasks}.  */
337      public static StartupTasks forStartupTasks(){
338        return (StartupTasks)forAbstraction(STARTUP_TASKS.getAbstraction());
339      }
340      
341      /** Return the configured implementation of {@link LoginTasks}.  */
342      public static LoginTasks forLoginTasks(){
343        return (LoginTasks)forAbstraction(LOGIN_TASKS.getAbstraction());
344      }
345      
346      /** Return the configured implementation of {@link ConvertParamError}.  */
347      public static ConvertParamError forConvertParamError(){
348        return (ConvertParamError)forAbstraction(CONVERT_PARAM_ERROR.getAbstraction());
349      }
350      
351      /** Return the configured implementation of {@link ConvertColumn}.  */
352      public static ConvertColumn forConvertColumn(){
353        return (ConvertColumn)forAbstraction(CONVERT_COLUMN.getAbstraction());
354      }
355    
356      /** Return the configured implementation of {@link PermittedCharacters}.  */
357      public static PermittedCharacters forPermittedCharacters(){
358        return (PermittedCharacters)forAbstraction(PERMITTED_CHARACTERS.getAbstraction());
359      }
360      
361      /** Return the configured implementation of {@link ConnectionSource}.  */
362      public static ConnectionSource forConnectionSource(){
363        return (ConnectionSource)forAbstraction(CONNECTION_SOURCE.getAbstraction());    
364      }
365      
366      /** Return the configured implementation of {@link LocaleSource}.  */
367      public static LocaleSource forLocaleSource(){
368        return (LocaleSource)forAbstraction(LOCALE_SRC.getAbstraction());
369      }
370    
371      /** 
372      Return the configured implementation of {@link TimeSource}.
373      
374      <P>When testing, an application may call this method in order to use a 'fake'
375      system time.
376      
377       <P>Internally, WEB4J will always use this method when it needs the current time.
378       This allows a fake system time to be shared between your application and WEB4J.
379      */
380      public static TimeSource forTimeSource(){
381        return (TimeSource)forAbstraction(TIME_SRC.getAbstraction());
382      }
383      
384      /** Return the configured implementation of {@link TimeZoneSource}.  */
385      public static TimeZoneSource forTimeZoneSource(){
386        return (TimeZoneSource)forAbstraction(TIME_ZONE_SRC.getAbstraction());
387      }
388      
389      /** Return the configured implementation of {@link DateConverter}.  */
390      public static DateConverter forDateConverter(){
391        return (DateConverter)forAbstraction(DATE_CONVERTER.getAbstraction());
392      }
393      
394      /** Return the configured implementation of {@link Translator}.  */
395      public static Translator forTranslator(){
396        return (Translator)forAbstraction(TRANSLATOR.getAbstraction());
397      }
398      
399      /** Return the configured implementation of {@link ApplicationFirewall}.  */
400      public static ApplicationFirewall forApplicationFirewall() {
401        return (ApplicationFirewall)forAbstraction(APP_FIREWALL.getAbstraction());
402      }
403      
404      /** Return the configured implementation of {@link SpamDetector}.  */
405      public static SpamDetector forSpamDetector() {
406        return (SpamDetector)forAbstraction(SPAM_DETECTOR.getAbstraction());
407      }
408      
409      /** Return the configured implementation of {@link Emailer}.  */
410      public static Emailer forEmailer() {
411        return (Emailer)forAbstraction(EMAILER.getAbstraction());
412      }
413      
414      /** Return the configured implementation of {@link ConvertParam}.  */
415      public static ConvertParam forConvertParam() {
416        return (ConvertParam)forAbstraction(CONVERT_PARAM.getAbstraction());
417      }
418      
419      /** Return the configured implementation of {@link UntrustedProxyForUserId}.  */
420      public static UntrustedProxyForUserId forOwnershipFirewall() {
421        return (UntrustedProxyForUserId)forAbstraction(OWNER_FIREWALL.getAbstraction());
422      }
423    
424      /**
425        Add an implementation - intended for testing only.
426          
427         <P>This method allows testing code to configure a specific implementation class. 
428         Example: <PRE>BuildImpl.adHocImplementationAdd(TimeSource.class, MyTimeSource.class);</PRE>
429         Calls to this method (often in a JUnit <tt>setUp()</tt> method) should be paired with a 
430         subsequent call to {@link #adHocImplementationRemove(Class)}. 
431      */
432      public static void adHocImplementationAdd(Class aInterface, Class aImplementationClass){
433         fClassMapping.put(aInterface.getName(), aImplementationClass);
434      }
435      
436      /**
437        Remove an implementation - intended for testing only.
438         
439        <P>This method allows testing code to configure a specific implementation class. 
440        Example: <PRE>BuildImpl.adHocImplementationRemove(TimeSource.class);</PRE> 
441        Calls to this method (often in a JUnit <tt>tearDown()</tt> method) should be paired with 
442        a previous call to {@link #adHocImplementationAdd(Class, Class)}. 
443      */
444      public static void adHocImplementationRemove(Class aInterface){
445        fClassMapping.remove(aInterface.getName());
446      }
447      
448      // PRIVATE 
449      
450      /**
451       Key - interface name (String)
452       Value - implementation class (Class) 
453      */
454      private static final Map<String, Class<?>> fClassMapping = new LinkedHashMap<String, Class<?>>();
455      
456      private static final String IMPLEMENTATION_FOR = "ImplementationFor.";
457      private static final Logger fLogger = Util.getLogger(BuildImpl.class);
458      
459      private BuildImpl(){
460        //prevent construction by caller
461      }
462      
463      /*
464       Implementation Note. 
465       Early versions of this class did not work with class literals. Problem disappeared?
466      */
467      
468      private static final String STANDARD_PACKAGE = "hirondelle.web4j.config.";
469      
470      //Items with no WEB4J default
471      private static final StandardDefault APPLICATION_INFO = new StandardDefault(ApplicationInfo.class.getName(),"AppInfo");
472      private static final StandardDefault STARTUP_TASKS = new StandardDefault(StartupTasks.class.getName(), "Startup");
473      private static final StandardDefault LOGIN_TASKS = new StandardDefault(LoginTasks.class.getName(), "Login");
474      private static final StandardDefault CONNECTION_SOURCE = new StandardDefault(ConnectionSource.class.getName(), "ConnectionSrc");
475      private static final StandardDefault CONVERT_PARAM_ERROR = new StandardDefault(ConvertParamError.class.getName(), "ConvertParamErrorImpl");
476      private static final StandardDefault TRANSLATOR = new StandardDefault(Translator.class.getName(), "TranslatorImpl");
477      private static final StandardDefault DATE_CONVERTER = new StandardDefault(DateConverter.class.getName(), "DateConverterImpl");
478      
479      //Items with a WEB4J default
480      private static final StandardDefault LOGGING_CONFIG = new StandardDefault(LoggingConfig.class.getName(), "LogConfig", LoggingConfigImpl.class.getName());
481      private static final StandardDefault REQUEST_PARSER = new StandardDefault(RequestParser.class.getName(), "RequestToAction", RequestParserImpl.class.getName());
482      private static final StandardDefault APP_FIREWALL = new StandardDefault(ApplicationFirewall.class.getName(), "AppFirewall", ApplicationFirewallImpl.class.getName());
483      private static final StandardDefault CONVERT_COLUMN = new StandardDefault(ConvertColumn.class.getName(), "ConvertColumns", ConvertColumnImpl.class.getName());
484      private static final StandardDefault LOCALE_SRC = new StandardDefault(LocaleSource.class.getName(), "LocaleSrc", LocaleSourceImpl.class.getName());
485      private static final StandardDefault TIME_SRC = new StandardDefault(TimeSource.class.getName(), "TimeSrc", TimeSourceImpl.class.getName());
486      private static final StandardDefault TIME_ZONE_SRC = new StandardDefault(TimeZoneSource.class.getName(), "TimeZoneSrc", TimeZoneSourceImpl.class.getName());
487      private static final StandardDefault SPAM_DETECTOR = new StandardDefault(SpamDetector.class.getName(), "SpamDetect", SpamDetectorImpl.class.getName());
488      private static final StandardDefault EMAILER = new StandardDefault(Emailer.class.getName(), "Email", EmailerImpl.class.getName());
489      private static final StandardDefault CONVERT_PARAM = new StandardDefault(ConvertParam.class.getName(), "ConvertParams", ConvertParamImpl.class.getName());
490      private static final StandardDefault PERMITTED_CHARACTERS = new StandardDefault(PermittedCharacters.class.getName(), "PermittedChars", PermittedCharactersImpl.class.getName());
491      private static final StandardDefault OWNER_FIREWALL = new StandardDefault(UntrustedProxyForUserId.class.getName(), "OwnerFirewall", UntrustedProxyForUserIdImpl.class.getName());
492      
493      //OTHERS? MUST add below as well...
494    
495      private static void useWebXmlSettingsFirst(ServletConfig aConfig) throws AppException {
496        Enumeration params = aConfig.getInitParameterNames();
497        while ( params.hasMoreElements() ){
498          String paramName = (String)params.nextElement();
499          if ( paramName.startsWith(IMPLEMENTATION_FOR) ) {
500            String interfaceName = paramName.substring(IMPLEMENTATION_FOR.length());
501            String className = aConfig.getInitParameter(paramName);
502            fClassMapping.put(interfaceName, buildWebXmlClass(className));
503          }
504        }
505      }
506      
507      private static void handleCtorProblem(Exception ex, Class<?> aImplementationClass){
508        String message = "Object construction by reflection failed for " + aImplementationClass.toString();
509        fLogger.severe(message);
510        throw new RuntimeException(message, ex);
511      }
512      
513      /** The system time must be done first, since used everywhere, including the logging system. */
514      private static void doTimeSourceConfig() throws AppException {
515        addStandardDefaultIfNotInWebXml(TIME_SRC); 
516      }
517      
518      /** Extract and execute the LoggingConfig, earlier than all others.  */
519      private static void doLoggingConfig(ServletConfig aConfig) throws AppException {
520        addStandardDefaultIfNotInWebXml(LOGGING_CONFIG); 
521        executeLoggingConfig(aConfig);
522      }
523      
524      private static void useStandardOrDefaultNameSecond() throws AppException {
525        fLogger.config("For items *not* specified in web.xml, searching for implementations with 'standard' name.");
526        fLogger.config("If no 'standard' implementation found, then will use the WEB4J 'default' implementation.");
527        //does NOT include the items done 'early'
528        addStandardDefaultIfNotInWebXml(APPLICATION_INFO);
529        addStandardDefaultIfNotInWebXml(CONNECTION_SOURCE);
530        addStandardDefaultIfNotInWebXml(CONVERT_PARAM_ERROR);
531        addStandardDefaultIfNotInWebXml(TRANSLATOR);
532        addStandardDefaultIfNotInWebXml(DATE_CONVERTER);
533        addStandardDefaultIfNotInWebXml(STARTUP_TASKS);
534        addStandardDefaultIfNotInWebXml(LOGIN_TASKS);
535        addStandardDefaultIfNotInWebXml(REQUEST_PARSER);
536        addStandardDefaultIfNotInWebXml(APP_FIREWALL);
537        addStandardDefaultIfNotInWebXml(CONVERT_COLUMN);
538        addStandardDefaultIfNotInWebXml(LOCALE_SRC);
539        addStandardDefaultIfNotInWebXml(TIME_ZONE_SRC);
540        addStandardDefaultIfNotInWebXml(SPAM_DETECTOR);
541        addStandardDefaultIfNotInWebXml(EMAILER);
542        addStandardDefaultIfNotInWebXml(CONVERT_PARAM);
543        addStandardDefaultIfNotInWebXml(PERMITTED_CHARACTERS);
544        addStandardDefaultIfNotInWebXml(OWNER_FIREWALL);
545      }
546      
547      private static boolean isAlreadySpecified(String aInterfaceName){
548        return fClassMapping.keySet().contains(aInterfaceName);
549      }
550    
551      private static Class<?> buildWebXmlClass(String aClassName) throws AppException {
552        Class<?> result = null;
553        try {
554          result = Class.forName(aClassName);
555        }
556        catch (ClassNotFoundException ex){
557          throw new AppException(
558            "Load of configured (or default) implementation class has failed. Class.forName() failed for " + Util.quote(aClassName), ex
559          );
560        }
561        return result;
562      }
563    
564      private static void addStandardDefaultIfNotInWebXml(StandardDefault aNames) throws AppException {
565        if ( ! isAlreadySpecified(aNames.getAbstraction()) ){
566          fClassMapping.put(aNames.getAbstraction(), buildStandardOrDefaultClass(aNames.getStandard(), aNames.getDefault()));
567        }
568      }
569      
570      private static Class<?> buildStandardOrDefaultClass(String aStandardName, String aDefaultName) throws AppException {
571        Class<?> result = null;
572        try {
573          result = Class.forName(aStandardName);
574        }
575        catch (ClassNotFoundException ex){
576          if( ! Util.textHasContent(aDefaultName) ){
577            throw new AppException(
578              "Load of configured implementation class has failed. Class.forName() failed for " + Util.quote(aStandardName), ex
579            );
580          }
581          fLogger.config("Cannot see any class named " + Util.quote(aStandardName) + ". Will use default WEB4J implementation instead, named " + Util.quote(aDefaultName));
582          try {
583            result = Class.forName(aDefaultName);
584          }
585          catch (ClassNotFoundException exception){
586            throw new AppException(
587              "Load of default implementation has failed. Class.forName() failed for " + Util.quote(aDefaultName), exception
588            );
589          }
590        }
591        return result;
592      }
593    
594      /** Execute the configured implementation of {@link LoggingConfig}.  */
595      private static void executeLoggingConfig(ServletConfig aConfig) throws AppException {
596        LoggingConfig loggingConfig = forLoggingConfig();
597        loggingConfig.setup(aConfig);
598      }
599      
600      /**  Return the configured implementation of {@link LoggingConfig}.  */ 
601      private static LoggingConfig forLoggingConfig(){
602        return (LoggingConfig)forAbstraction(LOGGING_CONFIG.getAbstraction());
603      }
604      
605      /** Gathers the standard and default class names related to an abstraction.   */
606      private static final class StandardDefault {
607        StandardDefault(String aAbstraction, String aStandard){
608          fAbstraction = aAbstraction;
609          fStandard = STANDARD_PACKAGE + aStandard;
610        }
611        StandardDefault(String aAbstraction, String aStandard, String aDefault){
612          fAbstraction = aAbstraction;
613          fStandard = STANDARD_PACKAGE + aStandard;
614          fDefault = aDefault;
615        }
616        boolean hasDefault(){
617          return Util.textHasContent(fDefault);
618        }
619        String getAbstraction() {  return fAbstraction;    }
620        String getDefault() {   return fDefault;   }
621        String getStandard() {    return fStandard;   }
622        private String fAbstraction;
623        private String fStandard;
624        private String fDefault;
625      }
626    }