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
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
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
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
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
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
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
745
746 /***
747 * @param servlet servlet for the adapter.
748 */
749 DefaultConfigAdapter(GenericServlet servlet) {
750 this.servlet = servlet;
751 }
752
753
754
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 }
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 }