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