View Javadoc

1   package pl.aislib.fm;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.OutputStream;
6   import java.io.Writer;
7   
8   import java.util.HashMap;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Map;
12  
13  import javax.servlet.GenericServlet;
14  import javax.servlet.ServletException;
15  import javax.servlet.http.HttpServlet;
16  import javax.servlet.http.HttpServletRequest;
17  import javax.servlet.http.HttpServletResponse;
18  import javax.servlet.http.HttpSession;
19  
20  import org.xml.sax.EntityResolver;
21  import org.xml.sax.InputSource;
22  import org.xml.sax.SAXException;
23  import org.xml.sax.XMLReader;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import pl.aislib.fm.shepherds.Pasture;
29  import pl.aislib.util.xml.LogErrorHandler;
30  import pl.aislib.util.xml.XMLUtils;
31  
32  /***
33   * Describes an application.
34   *
35   * Uses following components:
36   * <ul>
37   *   <li>{@link FormsHandler} -- describing all forms inside of application</li>
38   *   <li>{@link MessagesHandler} -- describing all applications messages</li>
39   *   <li>{@link Workflow} -- describing flow betweeen application pages</li>
40   * </ul>
41   *
42   * @author
43   * <table>
44   *   <tr><td><a href="mailto:pikus@ais.pl">Tomasz Pik</a>, AIS.PL</td></tr>
45   *   <tr><td><a href="mailto:warlock@ais.pl">Michal Jastak</a>, AIS.PL</td></tr>
46   * </table>
47   * @version $Revision: 1.14 $
48   * @since AISLIB 0.1
49   */
50  public class Application implements Pasture {
51  
52    /***
53     * Name of servlet context parameter whose value should identify application version.
54     */
55    private static final String APP_VERSION_CONTEXT_PARAM = "app.version";
56  
57    /***
58     * Default content type for responses returned in PageResponse.
59     */
60    private String defaultContentType = "text/html";
61  
62    /***
63     * Name of the application.
64     */
65    private String applicationName;
66  
67    /***
68     * Servlet that uses the application.
69     */
70    private HttpServlet servlet;
71  
72    /***
73     * {@link Workflow} component.
74     */
75    private Workflow workflow;
76  
77    /***
78     * {@link FormsHandler} component.
79     */
80    private FormsHandler forms;
81  
82    /***
83     * {@link MessagesHandler} component.
84     */
85    private MessagesHandler messages;
86  
87    /***
88     * {@link TemplateEngine} object for the application.
89     */
90    private TemplateEngine templateEngine;
91  
92    /***
93     * {@link ConfigAdapter} object for the application.
94     */
95    private ConfigAdapter configAdapter;
96  
97    /***
98     * {@link PageSelector} component.
99     */
100   private PageSelector pageSelector;
101 
102   /***
103    * {@link AttributeNameWrapper} object for the application.
104    */
105   private AttributeNameWrapper attributeNameWrapper;
106 
107   /***
108    * {@link Database} defined for the application.
109    */
110   private Database database;
111 
112   /***
113    * {@link Controller} object for the application.
114    */
115   private Controller controller;
116 
117   /***
118    * Main logging object for the application.
119    */
120   private Log mainLog;
121 
122   /***
123    * Logging object for workflow.
124    */
125   private Log workflowLog;
126 
127   /***
128    * Logging object for forms.
129    */
130   private Log formsLog;
131 
132   /***
133    * Logging object for messages.
134    */
135   private Log messagesLog;
136 
137   /***
138    * Logging object for templates.
139    */
140   private Log templateLog;
141 
142   // Constructors
143 
144   /***
145    * Main constructor.
146    *
147    * @param _applicationName name of the application.
148    * @param _servlet that requests this application handle.
149    * Throws NullPointerException if the <code>_applicationName</code> or <code>_servlet</code> is null
150    */
151   public Application(String _applicationName, HttpServlet _servlet) {
152     if (_applicationName == null) {
153       throw new NullPointerException("Application name cannot be null");
154     }
155     if (_servlet == null) {
156       throw new NullPointerException("servlet cannot be null");
157     }
158     applicationName = _applicationName;
159     servlet  = _servlet;
160 
161     configAdapter        = new DefaultConfigAdapter(servlet);
162     attributeNameWrapper = new AttributeNameWrapper(applicationName);
163     pageSelector         = new DefaultPageSelector();
164 
165     formsLog    = getLog("forms");
166     mainLog     = getLog();
167     messagesLog = getLog("messages");
168     templateLog = getLog("template");
169     workflowLog = getLog("workflow");
170 
171     workflow       = new Workflow(workflowLog);
172     forms          = new FormsHandler(formsLog);
173     messages       = new MessagesHandler(messagesLog);
174 
175     if (mainLog.isInfoEnabled() && (servlet.getServletContext() != null)) {
176       String version = servlet.getServletContext().getInitParameter(APP_VERSION_CONTEXT_PARAM);
177       mainLog.info("Version: " + version);
178     }
179   }
180 
181 
182   // Public methods
183 
184   /***
185    * Initialize {@link Workflow} component.
186    *
187    * @param workflowStream contains XML definition of application workflow
188    * @exception ApplicationConfigurationException if something goes wrong
189    */
190   public void initWorkflow(InputStream workflowStream) throws ApplicationConfigurationException {
191     if (workflowStream == null) {
192       throw new NullPointerException ("Invalid (null) configuration for Workflow");
193     }
194     initializeWorkflow(new InputSource(workflowStream));
195   }
196 
197   /***
198    * Initialize {@link Workflow} component.
199    *
200    * @param workflowSource contains XML definition of application workflow
201    * @exception ApplicationConfigurationException if something goes wrong
202    */
203   public void initWorkflow(InputSource workflowSource) throws ApplicationConfigurationException {
204     if (workflowSource == null) {
205       throw new NullPointerException ("Invalid (null) configuration for Workflow");
206     }
207     initializeWorkflow(workflowSource);
208   }
209 
210   private void initializeWorkflow(InputSource workflowSource) throws ApplicationConfigurationException {
211     if (mainLog.isInfoEnabled()) {
212       mainLog.info("initialize workflow");
213     }
214     XMLReader   workflowReader = null;
215     try {
216       workflowReader = XMLUtils.newXMLReader(true, true);
217     } catch (SAXException saxe) {
218       throw new ApplicationConfigurationException(saxe);
219     }
220     workflowReader.setContentHandler(workflow);
221     LogErrorHandler errorLog = new LogErrorHandler(workflowLog);
222     errorLog.setAllErrorsAreFatal(true);
223     workflowReader.setErrorHandler(errorLog);
224     EntityResolver jarEntityResolver = FmEntityResolver.getResolverInstance(workflowLog);
225     workflowReader.setEntityResolver(jarEntityResolver);
226     try {
227       workflowReader.parse(workflowSource);
228     } catch (IOException ioe) {
229       throw new ApplicationConfigurationException(ioe);
230     } catch (SAXException saxe) {
231       throw new ApplicationConfigurationException(saxe);
232     }
233     controller = new Controller(this, workflow);
234   }
235 
236   /***
237    * Initialize {@link MessagesHandler} component.
238    *
239    * @param messagesStream contains XML definition of application messages
240    * @exception ApplicationConfigurationException if something goes wrong
241    */
242   public void initMessages(InputStream messagesStream) throws ApplicationConfigurationException {
243     if (messagesStream == null) {
244       throw new NullPointerException ("Invalid (null) configuration for Messages");
245     }
246     initializeMessages(new InputSource(messagesStream));
247   }
248 
249   /***
250    * Initialize {@link MessagesHandler} component.
251    *
252    * @param messagesSource contains XML definition of application messages
253    * @exception ApplicationConfigurationException if something goes wrong
254    */
255   public void initMessages(InputSource messagesSource) throws ApplicationConfigurationException {
256     if (messagesSource == null) {
257       throw new NullPointerException ("Invalid (null) configuration for Messages");
258     }
259     initializeMessages(messagesSource);
260   }
261 
262   private void initializeMessages(InputSource messagesSource) throws ApplicationConfigurationException {
263     if (mainLog.isInfoEnabled()) {
264       mainLog.info("initialize messages");
265     }
266     XMLReader   messagesReader = null;
267     try {
268       messagesReader = XMLUtils.newXMLReader(true, true);
269     } catch (SAXException saxe) {
270       throw new ApplicationConfigurationException(saxe);
271     }
272     messagesReader.setContentHandler(messages);
273     LogErrorHandler errorLog = new LogErrorHandler (messagesLog);
274     errorLog.setAllErrorsAreFatal(true);
275     messagesReader.setErrorHandler(errorLog);
276     EntityResolver jarEntityResolver = FmEntityResolver.getResolverInstance(messagesLog);
277     messagesReader.setEntityResolver(jarEntityResolver);
278     try {
279       messagesReader.parse(messagesSource);
280     } catch (IOException ioe) {
281       throw new ApplicationConfigurationException(ioe);
282     } catch (SAXException saxe) {
283       throw new ApplicationConfigurationException(saxe);
284     }
285   }
286 
287   /***
288    * Initialize {@link FormsHandler} component.
289    *
290    * @param formsStream contains XML definition of application forms
291    * @exception ApplicationConfigurationException if something goes wrong
292    */
293   public void initForms(InputStream formsStream) throws ApplicationConfigurationException {
294     if (formsStream == null) {
295       throw new NullPointerException ("Invalid (null) configuration for Forms");
296     }
297     initializeForms(new InputSource(formsStream));
298   }
299 
300   /***
301    * Initialize {@link FormsHandler} component.
302    *
303    * @param formsSource contains XML definition of application forms
304    * @exception ApplicationConfigurationException if something goes wrong
305    */
306   public void initForms(InputSource formsSource) throws ApplicationConfigurationException {
307     if (formsSource == null) {
308       throw new NullPointerException ("Invalid (null) configuration for Forms");
309     }
310     initializeForms(formsSource);
311   }
312 
313   private void initializeForms(InputSource formsSource) throws ApplicationConfigurationException {
314     if (mainLog.isInfoEnabled()) {
315       mainLog.info("initialize forms");
316     }
317     XMLReader   formsReader = null;
318     try {
319       formsReader = XMLUtils.newXMLReader(true, true);
320     } catch (SAXException saxe) {
321       throw new ApplicationConfigurationException(saxe);
322     }
323     formsReader.setContentHandler(forms);
324     LogErrorHandler errorLog = new LogErrorHandler (formsLog);
325     errorLog.setAllErrorsAreFatal (true);
326     formsReader.setErrorHandler(errorLog);
327     EntityResolver jarEntityResolver = FmEntityResolver.getResolverInstance(formsLog);
328     formsReader.setEntityResolver(jarEntityResolver);
329     try {
330       formsReader.parse(formsSource);
331     } catch (IOException ioe) {
332       throw new ApplicationConfigurationException(ioe);
333     } catch (SAXException saxe) {
334       throw new ApplicationConfigurationException(saxe);
335     }
336   }
337 
338   /***
339    * Set {@link TemplateEngine} for this Application.
340    *
341    * @param _templateEngine which should be used to load templates.
342    */
343   public void setTemplateEngine(TemplateEngine _templateEngine) {
344     if (_templateEngine != null) {
345       if (mainLog.isInfoEnabled()) {
346         mainLog.info("setting template engine: " + _templateEngine.getClass().getName());
347       }
348       templateEngine = _templateEngine;
349     } else {
350       mainLog.error("try to set null as a template engine");
351       throw new NullPointerException("template engine cannot be null");
352     }
353   }
354 
355   /***
356    * Set {@link Database} for this Application
357    *
358    * During setting as a component inside Application, <code>jdbc</code>
359    * logger is pushed into given Database.
360    *
361    * @param _database for using within given Application.
362    * Throws NullPointerException if given argument is <code>null</code>
363    */
364   public void setDatabase(Database _database) {
365     if (_database != null) {
366       if (mainLog.isInfoEnabled()) {
367         mainLog.info("setting database: " + _database.getClass().getName());
368       }
369       database = _database;
370       database.setLog(getLog("jdbc"));
371     } else {
372       if (mainLog.isErrorEnabled()) {
373         mainLog.error("try to set null as a database");
374       }
375       throw new NullPointerException("database cannot be null");
376     }
377   }
378 
379   /***
380    * @return Database defined for Application.
381    */
382   public Database getDatabase() {
383     return database;
384   }
385 
386   /***
387    * Set {@link ConfigAdapter} for this Application.
388    *
389    * @param _configAdapter which should be used to access application parameters.
390    */
391   public void setConfigAdapter(ConfigAdapter _configAdapter) {
392     if (_configAdapter != null) {
393       if (mainLog.isInfoEnabled()) {
394         mainLog.info("setting config adapter: " + _configAdapter);
395       }
396       configAdapter = _configAdapter;
397     } else {
398       if (mainLog.isErrorEnabled()) {
399         mainLog.error("try to set null as a config adapter");
400       }
401       throw new NullPointerException("config adapter cannot be null");
402     }
403   }
404 
405   /***
406    * Set {@link PageSelector} for this Application.
407    *
408    * @param _pageSelector which slould be used for determinig page of user's request.
409    */
410   public void setPageSelector(PageSelector _pageSelector) {
411     if (_pageSelector != null) {
412       if (mainLog.isInfoEnabled()) {
413         mainLog.info("setting page selector: " + _pageSelector);
414       }
415       pageSelector = _pageSelector;
416     } else {
417       if (mainLog.isErrorEnabled()) {
418         mainLog.error("try to set null as a page selector");
419       }
420       throw new NullPointerException("page selector cannot be null");
421     }
422   }
423 
424   /***
425    * Sets default <code>ContentType</code> for the application.
426    *
427    * <code>text/html</code> is hard-coded as default one.
428    *
429    * @param _contentType default <code>ContentType</code>
430    */
431   public void setDefaultContentType(String _contentType) {
432     defaultContentType = _contentType;
433   }
434 
435   /***
436    * @return default <code>ContentType</code> for the application.
437    */
438   public String getDefaultContentType() {
439     return defaultContentType;
440   }
441 
442   /***
443    * @return the configuration adapter for the application.
444    */
445   public ConfigAdapter getConfigAdapter() {
446     return configAdapter;
447   }
448 
449   /***
450    *
451    * @return <code>AttributeNameWrapper</code> for the application
452    */
453   public AttributeNameWrapper getAttributeNameWrapper() {
454     return attributeNameWrapper;
455   }
456 
457   /***
458    * @return name of the application.
459    */
460   public String getName() {
461     return applicationName;
462   }
463 
464   /***
465    * @return servlet that uses the application.
466    */
467   public HttpServlet getServlet() {
468     return servlet;
469   }
470 
471   /***
472    * @return main logging object for the application.
473    */
474   public Log getLog() {
475     return LogFactory.getLog(applicationName);
476   }
477 
478   /***
479    * @param loggerName name of a logging object.
480    * @return a "sublogger" of the application's main logging object.
481    */
482   public Log getLog(String loggerName) {
483     return LogFactory.getLog(applicationName + "." + loggerName);
484   }
485 
486   /***
487    *
488    * @deprecated As of AISLIB 0.5 replaced by {@link Page#getMessage(int)} with default language.
489    * @see #getMessage(int, String).
490    */
491   public Message getMessage(int messageCode) {
492     Object obj = messages.getMessage(messageCode);
493     return obj != null ? (Message) obj : null;
494   }
495 
496   /***
497    * Method getMessage returns the message for given code and language.
498    *
499    * @param messageCode code of message.
500    * @param lang language of message content.
501    * @return Message for given <code>messageCode</code> and <code>lang</code>
502    *                  (if <code>lang</code> is null it returns default content).
503    */
504   public Message getMessage(int messageCode, String lang) {
505     Message message;
506     if (lang != null && lang.trim().length() > 0) {
507       message = messages.getMessage(messageCode, lang);
508     } else {
509       message = messages.getMessage(messageCode);
510     }
511     return message;
512   }
513 
514   /***
515    * @param groupCode code of message group.
516    * @return List of {@link Message} objects defined as group with given code.
517    */
518   public List getMessageGroup(int groupCode) {
519     return messages.getMessageGroup(groupCode);
520   }
521 
522   /***
523    * Gets form.
524    *
525    * @param formName name of a form.
526    * @return <code>Form</code> object.
527    * @deprecated As of AISLIB 0.5 replaced by {@link Page#getForm(String)} which supports multi language.
528    */
529   public Form getForm(String formName) {
530     return forms.getForm(formName, messages.messages, messages.messageGroups, null);
531   }
532 
533   /***
534    * Gets form.
535    *
536    * @param formName name of a form.
537    * @param language language for the form.
538    * @return <code>Form</code> object.
539    */
540   public Form getForm(String formName, String language) {
541     return forms.getForm(formName, messages.messages, messages.messageGroups, language);
542   }
543 
544   /***
545    * Process request and response.
546    *
547    * This method should always be used in <code>servlet</code> object.
548    *
549    * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
550    */
551   public void dispatch(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
552     if (controller == null) {
553       throw new ServletException("controller not initialized");
554     }
555 
556     String      pageName  = pageSelector.getRequestedPageKey(request);
557     HttpSession session   = request.getSession();
558 
559     PageInfo pageInfo = controller.getPageInfo(request, response);
560 
561     if (pageInfo == null) {
562       if (workflowLog.isDebugEnabled()) {
563         workflowLog.debug("unknown page for request, page attribute: " + pageName);
564       }
565       response.sendError(HttpServletResponse.SC_NOT_FOUND);
566       return;
567     }
568 
569     // create a new Page instance and initialize it through 'init' method
570     Page page = pageInfo.newPageInstance();
571     page.init(this, request, response, session);
572 
573     if (workflowLog.isDebugEnabled()) {
574       workflowLog.debug("getting response from page: " + pageName);
575     }
576 
577     PageResponse pageResponse = page.getPageResponse();
578 
579     if (pageResponse != null) {
580       pageInfo = pageResponse.getPage().pageInfo;
581       if (pageResponse.getContentType() != null) {
582         response.setContentType(pageResponse.getContentType());
583       } else if (defaultContentType != null) {
584         response.setContentType(defaultContentType);
585       }
586 
587       if (pageResponse.getContentByte() != null) {
588         OutputStream stream = response.getOutputStream();
589         stream.write(pageResponse.getContentByte());
590         return;
591       }
592 
593       Object masterTemplate = null;
594       Map    params         = pageResponse.getContentMap();
595       if (params == null) {
596         params = new HashMap();
597       }
598 
599       Map    includes = loadAndFill(page, pageInfo, params, request, response);
600       params.putAll(includes);
601 
602       if (pageInfo.getContainerTemplateName() == null) {
603         masterTemplate = loadTemplate(request, response, pageInfo.getTemplateName());
604       } else {
605         masterTemplate = loadTemplate(request, response, pageInfo.getContainerTemplateName());
606 
607         Object pageTemplate = loadTemplate(request, response, pageInfo.getTemplateName());
608 
609         try {
610           params.put(pageInfo.getContainerSlot(), templateEngine.evaluate(pageTemplate, params));
611         } catch (TemplateEngineException teex) {
612           throw new ServletException(teex.getMessage (), teex.getRootCause());
613         }
614       }
615 
616       String responseString = null;
617       try {
618         responseString = templateEngine.evaluate(masterTemplate, params);
619       } catch (TemplateEngineException teex) {
620         throw new ServletException(teex.getMessage(), teex.getRootCause());
621       }
622       Writer writer = response.getWriter();
623       writer.write((String) responseString);
624       return;
625     }
626   }
627 
628 
629   // Package methods
630 
631   /***
632    * @see TemplateEngine#load(Application, HttpServletRequest, HttpServletResponse, String)
633    */
634   Object loadTemplate(HttpServletRequest request, HttpServletResponse response, String templateName)
635     throws IOException, ServletException {
636     if (templateLog.isDebugEnabled()) {
637       templateLog.debug("loading template: " + templateName);
638     }
639     if (templateEngine == null) {
640       throw new NullPointerException ("Template Engine not initialized");
641     }
642     Object template = null;
643     try {
644       template = templateEngine.load(this, request, response, templateName);
645     } catch (TemplateEngineException teex) {
646       if (templateLog.isErrorEnabled()) {
647         String error = "exception caught during template '" + templateName + "' loading";
648         if (teex.getRootCause() != null) {
649           templateLog.error(error + ": " + teex.getMessage(), teex.getRootCause());
650         } else {
651           templateLog.error(error, teex);
652         }
653       }
654       if (teex.getRootCause() != null) {
655         if (teex.getRootCause() instanceof IOException) {
656           throw (IOException) teex.getRootCause();
657         }
658         if (teex.getRootCause() instanceof ServletException) {
659           throw (ServletException) teex.getRootCause();
660         }
661       }
662       throw new ServletException(teex.getMessage(), teex.getRootCause());
663     }
664     return template;
665   }
666 
667   /***
668    * @param actionKey name of a page.
669    * @return the page.
670    * @throws ServletException if an error occurs.
671    */
672   Page getPage(String actionKey) throws ServletException {
673     if (workflowLog.isDebugEnabled()) {
674       workflowLog.debug("getting page: " + actionKey);
675     }
676     PageInfo pageInfo = workflow.getPageInfo(actionKey);
677     if (pageInfo != null) {
678       Page page = pageInfo.newPageInstance();
679       page.pageInfo = pageInfo;
680       return page;
681     }
682     return null;
683   }
684 
685 
686   // Private methods
687 
688   /***
689    * Fills a template with values.
690    *
691    * @param page <code>Page</code> instance.
692    * @param pageInfo page information object.
693    * @param templateParams evaluation parameters.
694    * @param request <code>HttpServletRequest</code> object.
695    * @param response <code>HttpServletResponse</code> object.
696    * @return map of values.
697    * @throws IOException if an error occurs.
698    * @throws ServletException if an error occurs.
699    */
700   private Map loadAndFill(Page page, PageInfo pageInfo, Map templateParams, HttpServletRequest request,
701                           HttpServletResponse response) throws IOException, ServletException {
702     Map result = new HashMap();
703     Map templateIncludes = pageInfo.getTemplateIncludes();
704     Iterator keys = templateIncludes.keySet().iterator();
705     while (keys.hasNext()) {
706       String includeName         = (String) keys.next();
707       String includeTemplateName = (String) templateIncludes.get(includeName);
708       Object includeTemplate     = loadTemplate(request, response, includeTemplateName);
709       try {
710         result.put (includeName, templateEngine.evaluate (includeTemplate, templateParams));
711       } catch (TemplateEngineException teex) {
712         throw new ServletException (teex.getMessage (), teex.getRootCause ());
713       }
714     }
715 
716     Map messageIncludes = pageInfo.getMessageIncludes();
717     keys = messageIncludes.keySet().iterator();
718     while (keys.hasNext()) {
719       String  includeName = (String) keys.next();
720       Integer messageCode = (Integer) messageIncludes.get(includeName);
721       Message message = page.getMessage(messageCode.intValue());
722       result.put(includeName, message.getContent());
723     }
724     return result;
725   }
726 
727 
728   // Package classes
729 
730   /***
731    * Default implementation of {@link ConfigAdapter} class.
732    *
733    * Call {@link HttpServlet#getInitParameter(String)} and {@link javax.servlet.ServletContext#getInitParameter}
734    * methods.
735    */
736   class DefaultConfigAdapter extends ConfigAdapter {
737 
738     /***
739      * Servlet for the adapter.
740      */
741     private GenericServlet servlet;
742 
743 
744     // Constructors
745 
746     /***
747      * @param servlet servlet for the adapter.
748      */
749     DefaultConfigAdapter(GenericServlet servlet) {
750       this.servlet = servlet;
751     }
752 
753 
754     // Public methods
755 
756     /***
757      * Return standard parameter for a given servlet.
758      *
759      * @param key name of a parameter.
760      * @return value for the parameter or <code>null</code> if the parameter is not defined.
761      */
762     public String getConfigParameter(String key) {
763       String value = servlet.getInitParameter(key);
764       if (value != null) {
765         return value;
766       } else {
767         return servlet.getServletContext().getInitParameter(key);
768       }
769     }
770   } // DefaultConfigAdapter class
771 
772   class DefaultPageSelector implements PageSelector {
773 
774     private static final String PAGE = "page";
775 
776     public String getRequestedPageKey(HttpServletRequest request) {
777       return request.getParameter(PAGE);
778     }
779 
780   }
781 } // Application class