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 <init-param>
071 <param-name>ImplementationFor.hirondelle.web4j.ApplicationInfo</param-name>
072 <param-value>com.xyz.MyAppInfo</param-value>
073 <description>
074 Package-qualified name of class describing simple,
075 high level information about this application.
076 </description>
077 </init-param>
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 }