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