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