1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package net.sf.bimbo;
19
20 import static net.sf.bimbo.impl.HtmlUtil.escape;
21
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.security.Principal;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.UUID;
35
36 import javax.annotation.PostConstruct;
37 import javax.annotation.Resource;
38 import javax.naming.InitialContext;
39 import javax.naming.NamingException;
40 import javax.naming.NoInitialContextException;
41 import javax.servlet.ServletConfig;
42 import javax.servlet.ServletException;
43 import javax.servlet.http.HttpServlet;
44 import javax.servlet.http.HttpServletRequest;
45 import javax.servlet.http.HttpServletResponse;
46 import javax.servlet.http.HttpSession;
47
48 import net.sf.bimbo.impl.AccountPage;
49 import net.sf.bimbo.impl.BimboPrincipal;
50 import net.sf.bimbo.impl.BooleanRenderer;
51 import net.sf.bimbo.impl.DateRenderer;
52 import net.sf.bimbo.impl.DoubleRenderer;
53 import net.sf.bimbo.impl.FloatRenderer;
54 import net.sf.bimbo.impl.HtmlElement;
55 import net.sf.bimbo.impl.IntegerRenderer;
56 import net.sf.bimbo.impl.LoginPage;
57 import net.sf.bimbo.impl.StringRenderer;
58 import net.sf.bimbo.impl.StyleAdviceManager;
59 import net.sf.bimbo.spi.ConversionException;
60 import net.sf.bimbo.spi.Renderer;
61
62 import org.apache.commons.logging.Log;
63 import org.apache.commons.logging.LogFactory;
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class BimboServlet extends HttpServlet implements BimboContext {
79
80 private static final long serialVersionUID = 1L;
81
82 private static final Log LOG = LogFactory.getLog(BimboServlet.class);
83
84 private Class<?> welcomePageClass;
85
86 @Override
87 public void init(ServletConfig config) throws ServletException {
88 super.init(config);
89 String welcomePageInitParameter = config
90 .getInitParameter("WelcomePage");
91 if (null != welcomePageInitParameter) {
92 String welcomePageClassName = welcomePageInitParameter.trim();
93 this.welcomePageClass = loadClass(welcomePageClassName);
94 StyleAdviceManager.setWelcomePageClass(this.welcomePageClass);
95 }
96 }
97
98 public static Class<?> loadClass(String className) throws ServletException {
99 Thread currentThread = Thread.currentThread();
100 ClassLoader classLoader = currentThread.getContextClassLoader();
101 try {
102 return classLoader.loadClass(className);
103 } catch (ClassNotFoundException e) {
104 throw new ServletException("invalid page class: " + className);
105 }
106 }
107
108 @Override
109 protected void doGet(HttpServletRequest request,
110 HttpServletResponse response) throws ServletException, IOException {
111 LOG.debug("doGet");
112 PrintWriter writer = response.getWriter();
113 if (null == this.welcomePageClass) {
114 showErrorPage(writer);
115 } else {
116 Object pageObject = null;
117 try {
118 pageObject = createPage(this.welcomePageClass, request,
119 response);
120 } catch (Exception e) {
121 LOG.debug("create page error: " + e.getMessage(), e);
122 writer.println("Could not instantiate page class: "
123 + this.welcomePageClass.getName());
124 }
125 outputPage(this.welcomePageClass, pageObject, request, response);
126 }
127 }
128
129 private void outputPage(Class<?> pageClass, Object page,
130 HttpServletRequest request, HttpServletResponse response)
131 throws IOException {
132 PrintWriter writer = response.getWriter();
133 HttpSession session = request.getSession();
134 outputPage(pageClass, page, writer, session);
135 outputPage(pageClass, page, writer, session);
136 }
137
138 private void showErrorPage(PrintWriter writer) {
139 writer.println("<html>");
140 {
141 writer.println("<body>");
142 {
143 writer.println("<p>Not configured</p>");
144 }
145 writer.println("</body>");
146 }
147 writer.println("</html>");
148 }
149
150 private void outputPage(Class<?> pageClass, Object page,
151 PrintWriter writer, HttpSession session) {
152 outputPage(pageClass, page, session, null,
153 new HashMap<String, String>(), writer);
154 }
155
156 private String webappTitle = null;
157
158 private void outputPage(Class<?> pageClass, Object page,
159 HttpSession session, String message,
160 Map<String, String> constraintViolations, PrintWriter writer) {
161 Package pagePackage = pageClass.getPackage();
162 if (null != pagePackage) {
163 Title packageTitle = pagePackage.getAnnotation(Title.class);
164 if (null != packageTitle) {
165
166
167
168 this.webappTitle = packageTitle.value();
169 }
170 }
171
172 StyleAdviceManager styleAdviceManager = new StyleAdviceManager(
173 pageClass);
174
175 writer.println("<html>");
176
177 writer.println("<head>");
178 {
179 if (null != this.webappTitle) {
180 new HtmlElement("title").setBody(webappTitle).write(writer);
181 }
182 new HtmlElement("meta").addAttribute("name", "Identifier")
183 .addAttribute("content", UUID.randomUUID().toString())
184 .write(writer);
185 }
186 writer.println("</head>");
187
188 {
189 writer.println("<body>");
190 {
191 outputJavascript(pageClass, writer);
192 if (null != webappTitle) {
193 HtmlElement h1Element = new HtmlElement("h1")
194 .setBody(webappTitle);
195 String style = styleAdviceManager
196 .getApplicationTitleStyle();
197 h1Element.addAttribute("style", style);
198 h1Element.write(writer);
199 }
200 String username = (String) session
201 .getAttribute(USERNAME_SESSION_ATTRIBUTE);
202 String identityContent;
203 if (null == username) {
204 identityContent = "Welcome, Guest";
205 } else {
206 identityContent = "Welcome, "
207 + "<a href=\"javascript:doGlobalAction('account');\">"
208 + username
209 + "</a> "
210 + "<a href=\"javascript:doGlobalAction('logout');\">Logout</a>";
211 }
212 identityContent += " <a href=\"javascript:doGlobalAction('home');\">Home</a>";
213 new HtmlElement("div")
214 .addAttribute(
215 "style",
216 "position: absolute; right: 0%; text-align: right; background-color: #e0e0e0; border-style: solid; border-width: 1px; border-color: black; padding-left: 5px; padding-right: 5px;")
217 .setEscapedBody(identityContent).write(writer);
218 String pageTitle = getTitle(pageClass);
219 HtmlElement h2Element = new HtmlElement("h2")
220 .setBody(pageTitle);
221 h2Element.addAttribute("style", styleAdviceManager
222 .getPageTitleStyle());
223 h2Element.write(writer);
224
225 writer.println("<form name=\"GlobalActionForm\" action=\""
226 + pageClass.getSimpleName()
227 + ".bimbo\" method=\"POST\">");
228 writer
229 .println("<input type=\"hidden\" name=\"GlobalActionName\"/>");
230 writer.println("</form>");
231
232 writer.println("<form name=\"ActionForm\" action=\""
233 + pageClass.getSimpleName()
234 + ".bimbo\" method=\"POST\">");
235 {
236 new HtmlElement("input").addAttribute("type", "hidden")
237 .addAttribute("name", "PageClass").addAttribute(
238 "value", pageClass.getName()).write(writer);
239 new HtmlElement("input").addAttribute("type", "hidden")
240 .addAttribute("name", "ActionName").write(writer);
241 writePageContent(pageClass, page, constraintViolations,
242 message, writer);
243 }
244 writer.println("</form>");
245 }
246 writer.println("</body>");
247 }
248 writer.println("</html>");
249 }
250
251 private void outputJavascript(Class<?> pageClass, PrintWriter writer) {
252 writer.println("<script type=\"text/javascript\">");
253 {
254 writer.println("function doAction(name) {");
255 writer.println("\tdocument.ActionForm.ActionName.value = name;");
256 writer.println("\tdocument.ActionForm.submit();");
257 writer.println("}");
258 writer.println();
259 writer.println("function doConfirmAction(name, confirmation) {");
260 writer.println("\tvar answer = confirm(confirmation);");
261 writer.println("\tif (answer) {");
262 writer.println("\t\tdocument.ActionForm.ActionName.value = name;");
263 writer.println("\t\tdocument.ActionForm.submit();");
264 writer.println("\t}");
265 writer.println("}");
266 writer.println();
267 writer.println("function doGlobalAction(name) {");
268 writer
269 .println("\tdocument.GlobalActionForm.GlobalActionName.value = name;");
270 writer.println("\tdocument.GlobalActionForm.submit();");
271 writer.println("}");
272 writer.println();
273 writer.println("var req = false;");
274 writer.println("function doAjaxAction(name) {");
275 {
276 writer.println("\tif (window.XMLHttpRequest) {");
277 {
278 writer
279 .println("\t\ttry {req = new XMLHttpRequest();} catch(e) {}");
280 writer.println("\t} else {");
281 writer.println("\t\ttry {");
282 writer
283 .println("\t\t\treq = new ActiveXObject('Msxml2.XMLHTTP');");
284 writer.println("\t\t} catch(e) {");
285 writer
286 .println("\t\t\ttry {req = new ActiveXObject('Microsoft.XMLHTTP');} catch(e) {}");
287 writer.println("\t\t}");
288 }
289 writer.println("\t}");
290 }
291 writer.println("\tif (req) {");
292 writer
293 .println("document.getElementById('bimbo.ajax.message').innerHTML = 'Processing...';");
294 writer.println("var params='AjaxActionName=' + escape(name);");
295 writer.println("\t\treq.onreadystatechange = processAjaxResponse;");
296 writer.println("\t\treq.open('POST', '" + pageClass.getSimpleName()
297 + ".bimbo', true);");
298 writer
299 .println("\t\treq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');");
300 writer
301 .println("\t\treq.setRequestHeader('Content-Length', params.length);");
302 writer.println("\t\treq.setRequestHeader('Connection', 'close');");
303 writer.println("\t\treq.send(params);");
304 writer.println("\t}");
305 writer.println("}");
306 writer.println();
307 writer.println("function processAjaxResponse() {");
308 writer.println("\tif (req.readyState == 4) {");
309 writer.println("\t\tif (req.status == 200) {");
310 writer
311 .println("\t\t\tdocument.getElementById('bimbo.ajax.message').innerHTML = '';");
312 writer.println("\t\t}");
313 writer.println("\t}");
314 writer.println("}");
315 }
316 writer.println("</script>");
317 }
318
319 private interface GlobalAction {
320 void doAction(BimboServlet bimboServlet, HttpServletRequest request,
321 HttpServletResponse response) throws Exception;
322 }
323
324 private static final Map<String, GlobalAction> globalActions = new HashMap<String, GlobalAction>();
325
326 static {
327 globalActions.put("account", new ShowAccount());
328 globalActions.put("logout", new Logout());
329 globalActions.put("home", new ShowHome());
330 }
331
332 private static class ShowAccount implements GlobalAction {
333
334 public void doAction(BimboServlet bimboServlet,
335 HttpServletRequest request, HttpServletResponse response)
336 throws Exception {
337 AccountPage accountPage = bimboServlet.createPage(
338 AccountPage.class, request, response);
339 PrintWriter writer = response.getWriter();
340 HttpSession session = request.getSession();
341 bimboServlet.outputPage(AccountPage.class, accountPage, writer,
342 session);
343 }
344 }
345
346 private static class Logout implements GlobalAction {
347
348 public void doAction(BimboServlet bimboServlet,
349 HttpServletRequest request, HttpServletResponse response)
350 throws Exception {
351 HttpSession session = request.getSession();
352 session.removeAttribute(USERNAME_SESSION_ATTRIBUTE);
353 Object welcomePage = bimboServlet.createPage(
354 bimboServlet.welcomePageClass, request, response);
355 PrintWriter writer = response.getWriter();
356 bimboServlet.outputPage(bimboServlet.welcomePageClass, welcomePage,
357 writer, session);
358 }
359 }
360
361 private static class ShowHome implements GlobalAction {
362
363 public void doAction(BimboServlet bimboServlet,
364 HttpServletRequest request, HttpServletResponse response)
365 throws Exception {
366 HttpSession session = request.getSession();
367 Object welcomePage = bimboServlet.createPage(
368 bimboServlet.welcomePageClass, request, response);
369 PrintWriter writer = response.getWriter();
370 bimboServlet.outputPage(bimboServlet.welcomePageClass, welcomePage,
371 writer, session);
372 }
373 }
374
375 @Override
376 protected void doPost(HttpServletRequest request,
377 HttpServletResponse response) throws ServletException, IOException {
378 LOG.debug("doPost");
379 PrintWriter writer = response.getWriter();
380
381 String globalActionName = request.getParameter("GlobalActionName");
382 if (null != globalActionName) {
383 LOG.debug("global action: " + globalActionName);
384 GlobalAction globalAction = globalActions.get(globalActionName);
385 if (null == globalAction) {
386 writer
387 .println("unsupported global action: "
388 + globalActionName);
389 return;
390 }
391 try {
392 globalAction.doAction(this, request, response);
393 } catch (Exception e) {
394 writer.println("error executing global action: "
395 + globalActionName);
396 return;
397 }
398 return;
399 }
400
401 String ajaxActionName = request.getParameter("AjaxActionName");
402 if (null != ajaxActionName) {
403 LOG.debug("ajax action: " + ajaxActionName);
404
405 return;
406 }
407
408 String pageClassName = request.getParameter("PageClass");
409 LOG.debug("page class: " + pageClassName);
410 if (null == pageClassName) {
411 writer.println("need a PageClass parameter");
412 return;
413 }
414 Class<?> pageClass = loadClass(pageClassName);
415
416 String actionName = request.getParameter("ActionName");
417 LOG.debug("action name: " + actionName);
418 if (null == actionName) {
419 writer.println("need an ActionName parameter");
420 return;
421 }
422 Method actionMethod;
423 if (-1 != actionName.indexOf("(")) {
424 LOG.debug("table action requested");
425 String tableFieldName = actionName.substring(actionName
426 .indexOf("(") + 1, actionName.indexOf("."));
427 LOG.debug("table field name: " + tableFieldName);
428 Field tableField;
429 try {
430 tableField = pageClass.getDeclaredField(tableFieldName);
431 } catch (Exception e) {
432 writer.println("field not found: " + tableFieldName);
433 return;
434 }
435 if (false == List.class.equals(tableField.getType())) {
436 writer.println("field is not a list: " + tableFieldName);
437 return;
438 }
439 Integer tableActionIdx = Integer.parseInt(actionName.substring(
440 actionName.indexOf(".") + 1, actionName.indexOf(")")));
441 LOG.debug("table action idx: " + tableActionIdx);
442 String tableEntryType = request.getParameter(tableFieldName + "."
443 + tableActionIdx);
444 LOG.debug("table entry type: " + tableEntryType);
445 Class<?> tableEntryClass = loadClass(tableEntryType);
446 String actionMethodName = actionName.substring(0, actionName
447 .indexOf("("));
448 try {
449 actionMethod = pageClass.getDeclaredMethod(actionMethodName,
450 new Class[] { tableEntryClass });
451 } catch (Exception e) {
452 LOG.debug("no action method found for name: "
453 + actionMethodName);
454 writer.println("no action method found for name: "
455 + actionMethodName);
456 return;
457 }
458 } else {
459 try {
460 actionMethod = pageClass.getDeclaredMethod(actionName,
461 new Class[] {});
462 } catch (Exception e) {
463 writer
464 .println("no action method found for name: "
465 + actionName);
466 return;
467 }
468 }
469 Action actionAnnotation = actionMethod.getAnnotation(Action.class);
470 if (null == actionAnnotation) {
471 writer.println("action method not annotated with @Action: "
472 + actionName);
473 return;
474 }
475
476 HttpSession session = request.getSession();
477
478 Object page;
479 try {
480 page = createPage(pageClass, request, response);
481 } catch (Exception e) {
482 writer.println("could not init page: " + pageClassName
483 + ". Missing default constructor?");
484 return;
485 }
486
487 Map<String, String> conversionErrors;
488 try {
489 conversionErrors = restorePageFromRequest(request, pageClass, page);
490 } catch (Exception e) {
491 LOG.debug("error on restore: " + e.getMessage(), e);
492 throw new ServletException("error on restore: " + e.getMessage(), e);
493 }
494 if (false == conversionErrors.isEmpty()) {
495 LOG.debug("conversion errors: " + conversionErrors);
496 outputPage(pageClass, page, session, null, conversionErrors, writer);
497 return;
498 }
499
500 if (false == actionAnnotation.skipConstraints()) {
501 Map<String, String> constraintViolationFieldNames = checkConstraints(
502 pageClass, page);
503 if (false == constraintViolationFieldNames.isEmpty()) {
504 outputPage(pageClass, page, session, null,
505 constraintViolationFieldNames, writer);
506 return;
507 }
508 }
509
510 Authenticated authenticatedAnnotation = actionMethod
511 .getAnnotation(Authenticated.class);
512 if (null != authenticatedAnnotation) {
513 LOG.debug("authentication required");
514 String username = (String) session
515 .getAttribute(USERNAME_SESSION_ATTRIBUTE);
516 if (null == username) {
517 LOG.debug("login required");
518 LoginPage loginPage;
519 try {
520 loginPage = createPage(LoginPage.class, request, response);
521 } catch (Exception e) {
522 writer.println("could not init login page");
523 return;
524 }
525 LoginPage.saveActionPage(page, actionName, session);
526
527 outputPage(LoginPage.class, loginPage, writer, session);
528 return;
529 }
530 }
531
532 Object actionParam = null;
533 if (-1 != actionName.indexOf("(")) {
534 LOG.debug("table action requested");
535 String tableFieldName = actionName.substring(actionName
536 .indexOf("(") + 1, actionName.indexOf("."));
537 LOG.debug("table field name: " + tableFieldName);
538 Field tableField;
539 try {
540 tableField = pageClass.getDeclaredField(tableFieldName);
541 } catch (Exception e) {
542 writer.println("field not found: " + tableFieldName);
543 return;
544 }
545 if (false == List.class.equals(tableField.getType())) {
546 writer.println("field is not a list: " + tableFieldName);
547 return;
548 }
549 Integer tableActionIdx = Integer.parseInt(actionName.substring(
550 actionName.indexOf(".") + 1, actionName.indexOf(")")));
551 tableField.setAccessible(true);
552 List<?> table;
553 try {
554 table = (List<?>) tableField.get(page);
555 } catch (Exception e) {
556 writer.println("error reading list: " + tableFieldName);
557 return;
558 }
559 actionParam = table.get(tableActionIdx);
560 }
561 performAction(page, actionName, actionParam, session, writer, request,
562 response);
563 }
564
565 public void performAction(Object page, String actionName,
566 Object actionParam, HttpSession session, PrintWriter writer,
567 HttpServletRequest request, HttpServletResponse response)
568 throws IOException {
569 Class<?> pageClass = page.getClass();
570 Method actionMethod;
571 if (null != actionParam) {
572 Class<?> actionParamType = actionParam.getClass();
573 try {
574 actionMethod = pageClass.getDeclaredMethod(actionName
575 .substring(0, actionName.indexOf("(")),
576 new Class[] { actionParamType });
577 } catch (Exception e) {
578 LOG.debug("no action method found for name: " + actionName
579 + " and param type " + actionParamType.getName());
580 writer.println("no action method found for name: " + actionName
581 + " and param type " + actionParamType.getName());
582 return;
583 }
584 } else {
585 try {
586 actionMethod = pageClass.getDeclaredMethod(actionName,
587 new Class[] {});
588 } catch (Exception e) {
589 writer
590 .println("no action method found for name: "
591 + actionName);
592 return;
593 }
594 }
595 Action actionAnnotation = actionMethod.getAnnotation(Action.class);
596 if (null == actionAnnotation) {
597 writer.println("action method not annotated with @Action: "
598 + actionName);
599 return;
600 }
601 Object resultPage;
602 try {
603 if (null == actionParam) {
604 resultPage = actionMethod.invoke(page, new Object[] {});
605 } else {
606 resultPage = actionMethod.invoke(page,
607 new Object[] { actionParam });
608 }
609 } catch (InvocationTargetException e) {
610 String actionLabel;
611 if ("".equals(actionAnnotation.value())) {
612 actionLabel = actionMethod.getName();
613 } else {
614 actionLabel = actionAnnotation.value();
615 }
616 Throwable targetException = e.getTargetException();
617 Map<String, String> constraintMessages = blameInputFields(page,
618 targetException);
619 LOG.debug("# blamed fields: " + constraintMessages.size());
620 String message;
621 if (constraintMessages.isEmpty()) {
622 message = actionLabel + " Error: "
623 + targetException.getMessage();
624 LOG.debug("error message: " + message);
625 } else {
626 LOG.debug("contraint messages: " + constraintMessages);
627 message = null;
628 }
629 outputPage(pageClass, page, session, message, constraintMessages,
630 writer);
631 return;
632 } catch (Exception e) {
633 LOG.debug("error invoking action: " + e.getMessage());
634 LOG.debug("exception type: " + e.getClass().getName());
635 writer.println("error invoking action: " + actionName);
636 return;
637 } finally {
638 saveSessionAttribute(pageClass, page, session);
639 }
640 if (null == resultPage) {
641
642
643
644 return;
645 }
646
647 Class<?> resultPageClass = resultPage.getClass();
648 try {
649 injectResources(resultPageClass, request, response, resultPage);
650 injectSessionAttributes(resultPageClass, request, resultPage);
651 executePostConstruct(resultPageClass, resultPage);
652 } catch (Exception e) {
653 LOG.debug("error: " + e.getMessage());
654 LOG.debug("exception type: " + e.getClass().getName());
655 writer.println(e.getMessage());
656 return;
657 }
658
659 LOG.debug("result page class: " + resultPageClass.getSimpleName());
660
661 outputPage(resultPageClass, resultPage, writer, session);
662 }
663
664 private Map<String, String> blameInputFields(Object page,
665 Throwable exception) {
666 Map<String, String> constraintMessages = new HashMap<String, String>();
667 Class<?> pageClass = page.getClass();
668 Field[] fields = pageClass.getDeclaredFields();
669 LOG.debug("exception type: " + exception.getClass().getName());
670 for (Field field : fields) {
671 Input inputAnnotation = field.getAnnotation(Input.class);
672 if (null == inputAnnotation) {
673 continue;
674 }
675 BlameMe blameMeAnnotation = field.getAnnotation(BlameMe.class);
676 if (null == blameMeAnnotation) {
677 continue;
678 }
679 LOG.debug("blame input field found: " + field.getName());
680 for (Class<? extends Exception> exceptionClass : blameMeAnnotation
681 .value()) {
682 if (exceptionClass.equals(exception.getClass())) {
683 String fieldName = field.getName();
684 LOG.debug(fieldName + ": blame me for exception: "
685 + exceptionClass.getName());
686 constraintMessages.put(fieldName, exception.getMessage());
687 }
688 }
689 }
690 return constraintMessages;
691 }
692
693 private void saveSessionAttribute(Class<?> pageClass, Object page,
694 HttpSession session) {
695 Field[] fields = pageClass.getDeclaredFields();
696 for (Field field : fields) {
697 SessionAttribute sessionAttributeAnnotation = field
698 .getAnnotation(SessionAttribute.class);
699 if (null == sessionAttributeAnnotation) {
700 continue;
701 }
702 String attributeName = sessionAttributeAnnotation.value();
703 if ("".equals(attributeName)) {
704 attributeName = field.getName();
705 }
706 field.setAccessible(true);
707 Object attributeValue = null;
708 try {
709 attributeValue = field.get(page);
710 } catch (Exception e) {
711 LOG.debug("error reading session attribute field: "
712 + field.getName());
713 }
714 LOG.debug("saving session attribute:" + attributeName);
715 session.setAttribute(attributeName, attributeValue);
716 }
717 }
718
719 public static final String USERNAME_SESSION_ATTRIBUTE = BimboServlet.class
720 .getName()
721 + ".USERNAME";
722
723 public <T> T createPage(Class<T> pageClass, HttpServletRequest request,
724 HttpServletResponse response) throws InstantiationException,
725 IllegalAccessException, IllegalArgumentException, NamingException,
726 ServletException {
727 LOG.debug("create page: " + pageClass.getSimpleName());
728 T page = pageClass.newInstance();
729 injectResources(pageClass, request, response, page);
730 injectSessionAttributes(pageClass, request, page);
731 executePostConstruct(pageClass, page);
732 return page;
733 }
734
735 private void injectSessionAttributes(Class<?> pageClass,
736 HttpServletRequest request, Object page)
737 throws IllegalArgumentException, IllegalAccessException {
738 Field[] fields = pageClass.getDeclaredFields();
739 HttpSession session = null;
740 for (Field field : fields) {
741 SessionAttribute sessionAttributeAnnotation = field
742 .getAnnotation(SessionAttribute.class);
743 if (null == sessionAttributeAnnotation) {
744 continue;
745 }
746 String attributeName = sessionAttributeAnnotation.value();
747 if ("".equals(attributeName)) {
748 attributeName = field.getName();
749 }
750 if (null == session) {
751 session = request.getSession();
752 }
753 Object attributeValue = session.getAttribute(attributeName);
754 field.setAccessible(true);
755 LOG.debug("restore session attribute: " + attributeName);
756 field.set(page, attributeValue);
757 }
758 }
759
760 private void executePostConstruct(Class<?> pageClass, Object page)
761 throws ServletException {
762 Method[] methods = pageClass.getDeclaredMethods();
763 for (Method method : methods) {
764 PostConstruct postConstructAnnotation = method
765 .getAnnotation(PostConstruct.class);
766 if (null == postConstructAnnotation) {
767 continue;
768 }
769 try {
770 LOG.debug("invoking @PostConstruct: " + method.getName());
771 method.setAccessible(true);
772 method.invoke(page, new Object[] {});
773 } catch (Exception e) {
774 LOG.debug("error invoking postcontruct method: "
775 + e.getMessage(), e);
776 throw new ServletException(
777 "Could not execute PostConstruct method: "
778 + method.getName());
779 }
780 }
781 }
782
783 private void injectResources(Class<?> pageClass,
784 HttpServletRequest request, HttpServletResponse response,
785 Object page) throws NamingException, IllegalArgumentException,
786 IllegalAccessException, ServletException {
787 Field[] fields = pageClass.getDeclaredFields();
788 InitialContext initialContext = null;
789 for (Field field : fields) {
790 Resource resourceAnnotation = field.getAnnotation(Resource.class);
791 if (null == resourceAnnotation) {
792 continue;
793 }
794 if (HttpSession.class.equals(field.getType())) {
795 HttpSession session = request.getSession();
796 LOG.debug("injecting http session into field "
797 + field.getName());
798 field.setAccessible(true);
799 field.set(page, session);
800 continue;
801 }
802 if (HttpServletRequest.class.equals(field.getType())) {
803 LOG.debug("injecting http request into field "
804 + field.getName());
805 field.setAccessible(true);
806 field.set(page, request);
807 continue;
808 }
809 if (HttpServletResponse.class.equals(field.getType())) {
810 LOG.debug("injecting http response into field "
811 + field.getName());
812 field.setAccessible(true);
813 field.set(page, response);
814 continue;
815 }
816 if (Principal.class.equals(field.getType())) {
817 LOG.debug("injecting principal into field: " + field.getName());
818 HttpSession session = request.getSession();
819 String username = (String) session
820 .getAttribute(USERNAME_SESSION_ATTRIBUTE);
821 if (null == username) {
822 username = "anonymous";
823 }
824 BimboPrincipal principal = new BimboPrincipal(username);
825 field.setAccessible(true);
826 field.set(page, principal);
827 continue;
828 }
829 if (BimboContext.class.equals(field.getType())) {
830 LOG.debug("injecting this bimbo context");
831 field.setAccessible(true);
832 field.set(page, this);
833 continue;
834 }
835 String resourceName = resourceAnnotation.name();
836 if (null == initialContext) {
837 initialContext = new InitialContext();
838 }
839 Object resource;
840 try {
841 resource = initialContext.lookup(resourceName);
842 } catch (NoInitialContextException e) {
843 LOG.debug("no initial context: " + e.getMessage());
844 throw new ServletException("no initial context");
845 }
846 if (null == resource) {
847 throw new ServletException("resource " + resourceName
848 + " is null");
849 }
850 LOG.debug("injecting resource " + resourceName + " into field "
851 + field.getName());
852 field.setAccessible(true);
853 field.set(page, resource);
854 }
855 }
856
857
858
859
860
861
862
863 private Map<String, String> checkConstraints(Class<?> pageClass, Object page)
864 throws ServletException {
865 Map<String, String> constraintViolations = new HashMap<String, String>();
866 Field[] fields = pageClass.getDeclaredFields();
867 LOG.debug("check constraints: " + pageClass.getSimpleName());
868 for (Field field : fields) {
869 Constraint constraintAnnotation = field
870 .getAnnotation(Constraint.class);
871 if (null == constraintAnnotation) {
872 continue;
873 }
874 if (constraintAnnotation.required()) {
875 LOG.debug("constraint check for field: " + field.getName());
876 field.setAccessible(true);
877 Object value;
878 try {
879 value = field.get(page);
880 } catch (Exception e) {
881 throw new ServletException("could not read field: "
882 + field.getName() + " of type "
883 + field.getType().getName());
884 }
885 if (String.class.equals(field.getType())) {
886 String strValue = (String) value;
887 if ("".equals(strValue.trim())) {
888 LOG.debug("field " + field.getName() + " is required");
889 constraintViolations.put(field.getName(),
890 "Value required.");
891 }
892 } else if (field.getType().isPrimitive()) {
893
894
895
896 } else {
897 throw new ServletException(
898 "constraint violation: field type not supported: "
899 + field.getType().getName());
900 }
901 }
902 }
903 return constraintViolations;
904 }
905
906
907
908
909
910
911
912
913 private Map<String, String> restorePageFromRequest(
914 HttpServletRequest request, Class<?> pageClass, Object page)
915 throws ServletException {
916 LOG.debug("restore page: " + pageClass.getSimpleName());
917 Map<String, String> conversionErrors = new HashMap<String, String>();
918 Field[] fields = pageClass.getDeclaredFields();
919 for (Field field : fields) {
920 if (Modifier.FINAL == (field.getModifiers() & Modifier.FINAL)) {
921 continue;
922 }
923 Input inputAnnotation = field.getAnnotation(Input.class);
924 Output outputAnnotation = field.getAnnotation(Output.class);
925 if (null != inputAnnotation || null != outputAnnotation) {
926 String fieldName = field.getName();
927 LOG.debug("restore field: " + fieldName);
928 Render renderAnnotation = field.getAnnotation(Render.class);
929 if (null != renderAnnotation) {
930 Class<? extends Renderer<?>> rendererClass = renderAnnotation
931 .value();
932 Renderer<?> renderer = null;
933 try {
934 renderer = rendererClass.newInstance();
935 } catch (Exception e) {
936 throw new ServletException(
937 "could not init renderer class: "
938 + rendererClass.getName());
939 }
940 if (null != renderer) {
941 Object value;
942 try {
943 value = renderer.restore(fieldName, request);
944 } catch (ConversionException e) {
945 conversionErrors.put(fieldName, e.getMessage());
946 continue;
947 }
948 field.setAccessible(true);
949 try {
950 field.set(page, value);
951 } catch (Exception e) {
952 throw new ServletException("could not set field: "
953 + fieldName, e);
954 }
955 }
956 continue;
957 } else if (List.class.equals(field.getType())) {
958 LOG.debug("table detected");
959 String tableType = request
960 .getParameter(fieldName + ".type");
961 List<Object> list = new LinkedList<Object>();
962 if (null != tableType) {
963 Class<?> tableEntryClass = loadClass(tableType);
964 if (String.class.equals(tableEntryClass)) {
965 int idx = 0;
966 String value;
967 while (null != (value = request
968 .getParameter(fieldName + "." + idx))) {
969 list.add(value);
970 idx++;
971 }
972 } else {
973 int idx = 0;
974 while (null != (request.getParameter(fieldName
975 + "." + idx))) {
976 Object tableEntry;
977 try {
978 tableEntry = tableEntryClass.newInstance();
979 } catch (Exception e) {
980 LOG.debug("could not init table entry: "
981 + tableEntryClass.getName());
982 throw new ServletException(
983 "could not init table entry: "
984 + tableEntryClass.getName());
985 }
986 Field[] tableEntryFields = tableEntryClass
987 .getDeclaredFields();
988 for (Field tableEntryField : tableEntryFields) {
989 if (null != tableEntryField
990 .getAnnotation(Output.class)) {
991 String pageValue = request
992 .getParameter(fieldName
993 + "."
994 + idx
995 + "."
996 + tableEntryField
997 .getName());
998 Object value;
999 if (Integer.TYPE.equals(tableEntryField
1000 .getType())) {
1001 value = Integer.parseInt(pageValue);
1002 } else if (Float.TYPE
1003 .equals(tableEntryField
1004 .getType())) {
1005 value = Float.parseFloat(pageValue);
1006 } else if (Double.TYPE
1007 .equals(tableEntryField
1008 .getType())) {
1009 value = Double
1010 .parseDouble(pageValue);
1011 } else {
1012 value = pageValue;
1013 }
1014 tableEntryField.setAccessible(true);
1015 try {
1016 tableEntryField.set(tableEntry,
1017 value);
1018 } catch (Exception e) {
1019 LOG
1020 .debug("table entry field: could not set field: "
1021 + tableEntryField
1022 .getName());
1023 throw new ServletException(
1024 "table entry field: could not set field: "
1025 + tableEntryField
1026 .getName());
1027 }
1028 }
1029 }
1030 list.add(tableEntry);
1031 idx++;
1032 }
1033 }
1034 }
1035 field.setAccessible(true);
1036 try {
1037 LOG.debug("restore table field: " + fieldName);
1038 field.set(page, list);
1039 } catch (Exception e) {
1040 LOG.debug("restore table field: could not set field: "
1041 + fieldName);
1042 throw new ServletException(
1043 "restore table field: could not set field: "
1044 + fieldName);
1045 }
1046 continue;
1047 }
1048 String fieldPageValue = request.getParameter(fieldName);
1049 Object fieldValue;
1050 Class<? extends Renderer<?>> rendererClass = typeRenderers
1051 .get(field.getType());
1052 if (null != rendererClass) {
1053 Renderer<?> renderer;
1054 try {
1055 renderer = rendererClass.newInstance();
1056 } catch (Exception e) {
1057 throw new ServletException(
1058 "could not init renderer class: "
1059 + rendererClass.getName());
1060 }
1061 try {
1062 fieldValue = renderer.restore(fieldName, request);
1063 } catch (ConversionException e) {
1064 conversionErrors.put(fieldName, e.getMessage());
1065 continue;
1066 }
1067 } else if (field.getType().isEnum()) {
1068 Object[] enumConstants = field.getType().getEnumConstants();
1069 fieldValue = null;
1070 for (Object enumConstant : enumConstants) {
1071 Enum<?> enumClass = (Enum<?>) enumConstant;
1072 if (fieldPageValue.equals(enumClass.name())) {
1073 fieldValue = enumConstant;
1074 break;
1075 }
1076 }
1077 if (null == fieldValue) {
1078 throw new ServletException("invalid enum value: "
1079 + fieldPageValue);
1080 }
1081 } else if (hasOutputFields(field.getType())) {
1082 fieldValue = restoreFromRecord(request, field);
1083 } else {
1084 LOG.debug("restore page: field type not supported: "
1085 + field.getType());
1086 throw new ServletException(
1087 "restore page: field type not supported: "
1088 + field.getType());
1089 }
1090 field.setAccessible(true);
1091 try {
1092 field.set(page, fieldValue);
1093 } catch (Exception e) {
1094 LOG.debug("restore: could not set field: " + fieldName);
1095 throw new ServletException("restore: could not set field: "
1096 + fieldName);
1097 }
1098 }
1099 }
1100 LOG.debug("page restored");
1101 return conversionErrors;
1102 }
1103
1104 private Object restoreFromRecord(HttpServletRequest request, Field field)
1105 throws ServletException {
1106 Object fieldValue;
1107 try {
1108 fieldValue = field.getType().newInstance();
1109 } catch (Exception e) {
1110 throw new ServletException("cannot init type: "
1111 + field.getType().getName());
1112 }
1113 Field[] recordFields = field.getType().getDeclaredFields();
1114 for (Field recordField : recordFields) {
1115 Output recordOutputAnnotation = recordField
1116 .getAnnotation(Output.class);
1117 if (null == recordOutputAnnotation) {
1118 continue;
1119 }
1120 LOG.debug("restore " + field.getName() + "."
1121 + recordField.getName());
1122 Class<? extends Renderer<?>> rendererClass = typeRenderers
1123 .get(recordField.getType());
1124 if (null == rendererClass) {
1125 throw new ServletException("no renderer class for type: "
1126 + recordField.getType());
1127 }
1128 Renderer<?> renderer;
1129 try {
1130 renderer = rendererClass.newInstance();
1131 } catch (Exception e) {
1132 throw new ServletException("renderer init error: "
1133 + e.getMessage(), e);
1134 }
1135 Object recordValue;
1136 try {
1137 recordValue = renderer.restore(field.getName() + "."
1138 + recordField.getName(), request);
1139 } catch (ConversionException e) {
1140 LOG.debug("error restoring from record: " + e.getMessage(), e);
1141 throw new ServletException("error restoring from record: "
1142 + field.getName(), e);
1143 }
1144 recordField.setAccessible(true);
1145 try {
1146 recordField.set(fieldValue, recordValue);
1147 } catch (Exception e) {
1148 throw new ServletException("error on restore record field: "
1149 + recordField.getName());
1150 }
1151 }
1152 return fieldValue;
1153 }
1154
1155 private void writePageContent(Class<?> pageClass, Object pageObject,
1156 Map<String, String> constraintViolations, String errorMessage,
1157 PrintWriter writer) {
1158 LOG.debug("write page content for page: " + pageClass.getSimpleName());
1159 outputFields(pageClass, pageObject, constraintViolations, writer);
1160 outputMessages(writer, errorMessage);
1161 outputActions(pageClass, writer);
1162 }
1163
1164 private void outputFields(Class<?> pageClass, Object pageObject,
1165 Map<String, String> constraintViolations, PrintWriter writer) {
1166 writer.println("<table>");
1167 Field[] fields = pageClass.getDeclaredFields();
1168 for (Field field : fields) {
1169 Output outputAnnotation = field.getAnnotation(Output.class);
1170 if (null != outputAnnotation) {
1171 writeOutputField(field, outputAnnotation, pageClass,
1172 pageObject, writer);
1173 }
1174 Input inputAnnotation = field.getAnnotation(Input.class);
1175 if (null != inputAnnotation) {
1176 String fieldName = field.getName();
1177 String constraintViolation;
1178 if (constraintViolations.containsKey(fieldName)) {
1179 constraintViolation = constraintViolations.get(fieldName);
1180 if (null == constraintViolation) {
1181
1182
1183
1184
1185 constraintViolation = "null";
1186 }
1187 } else {
1188 constraintViolation = null;
1189 }
1190 writeInputField(field, inputAnnotation, pageObject,
1191 constraintViolation, writer);
1192 }
1193
1194 }
1195 writer.println("</table>");
1196 }
1197
1198 private void outputActions(Class<?> pageClass, PrintWriter writer) {
1199 writer.println("<div>");
1200 Method[] methods = pageClass.getDeclaredMethods();
1201 for (Method method : methods) {
1202 Action actionAnnotation = method.getAnnotation(Action.class);
1203 if (null == actionAnnotation) {
1204 continue;
1205 }
1206 if (0 != method.getParameterTypes().length) {
1207
1208
1209
1210 continue;
1211 }
1212 String methodName = method.getName();
1213 String actionName = actionAnnotation.value();
1214 if ("".equals(actionName)) {
1215 actionName = methodName;
1216 }
1217 HtmlElement inputElement = new HtmlElement("input").addAttribute(
1218 "type", "button").addAttribute("name", methodName)
1219 .addAttribute("value", actionName);
1220 String confirmationMessage = actionAnnotation.confirmation();
1221 Class<?> returnType = method.getReturnType();
1222 if (Void.TYPE.equals(returnType)) {
1223 LOG.debug("ajax method");
1224 inputElement.addAttribute("onclick", "doAjaxAction('"
1225 + methodName + "');");
1226 } else {
1227 if ("".equals(confirmationMessage)) {
1228 inputElement.addAttribute("onclick", "doAction('"
1229 + methodName + "');");
1230 } else {
1231 inputElement
1232 .addAttribute("onclick", "doConfirmAction('"
1233 + methodName + "', '" + confirmationMessage
1234 + "');");
1235 }
1236 }
1237 inputElement.write(writer);
1238 }
1239 writer.println("</div>");
1240 }
1241
1242 private void outputMessages(PrintWriter writer, String message) {
1243
1244
1245
1246 writer.println("<div id=\"bimbo.ajax.message\"></div>");
1247 if (null == message) {
1248 return;
1249 }
1250 writer.println("<p>");
1251 {
1252 new HtmlElement("div")
1253 .addAttribute(
1254 "style",
1255 "color: red; background-color: #ffe0e0; display: inline; border-style: solid; border-width: 1px; border-color: red; padding-left: 5px; padding-right: 5px;")
1256 .setBody(message).write(writer);
1257 }
1258 writer.println("</p>");
1259 }
1260
1261 private void writeOutputField(Field field, Output outputAnnotation,
1262 Class<?> pageClass, Object pageObject, PrintWriter writer) {
1263 writer.println("<tr>");
1264 {
1265 String outputLabel = outputAnnotation.value();
1266 if (false == "".equals(outputLabel)) {
1267 new HtmlElement("th").addAttribute("align", "left")
1268 .addAttribute("style", "background-color: #e0e0e0")
1269 .setBody(outputLabel + ":").write(writer);
1270 writer.println("<td>");
1271 } else {
1272 writer.println("<td colspan=\"2\">");
1273 }
1274 String fieldName = field.getName();
1275 try {
1276 field.setAccessible(true);
1277 Object outputValue = field.get(pageObject);
1278 Render renderAnnotation = field.getAnnotation(Render.class);
1279 if (null != renderAnnotation) {
1280 Class<? extends Renderer<?>> rendererClass = renderAnnotation
1281 .value();
1282 Renderer renderer = rendererClass.newInstance();
1283 renderer.renderOutput(fieldName, outputValue, writer);
1284 } else if (outputAnnotation.verbatim()) {
1285 new HtmlElement("pre").setBody(outputValue.toString())
1286 .write(writer);
1287 new HtmlElement("input").addAttribute("type", "hidden")
1288 .addAttribute("name", fieldName).addAttribute(
1289 "value", outputValue.toString()).write(
1290 writer);
1291 } else if (List.class.equals(field.getType())) {
1292 List<?> outputList = (List<?>) outputValue;
1293 outputTable(fieldName, outputList, pageClass, writer);
1294 } else {
1295 Class<?> outputClass;
1296 if (null != outputValue) {
1297 outputClass = outputValue.getClass();
1298 } else {
1299 outputClass = field.getType();
1300 }
1301 if (hasOutputFields(outputClass)) {
1302 writeOutputRecordField(field, outputValue, outputClass,
1303 writer);
1304 } else {
1305 if (null == outputValue) {
1306 outputValue = "";
1307 }
1308 writer.println(escape(outputValue));
1309 new HtmlElement("input").addAttribute("type", "hidden")
1310 .addAttribute("name", fieldName).addAttribute(
1311 "value", outputValue.toString()).write(
1312 writer);
1313 }
1314 }
1315 } catch (Exception e) {
1316 LOG.debug("cannot read field " + fieldName + ": "
1317 + e.getMessage());
1318 writer.println("cannot read field: " + fieldName);
1319 }
1320 writer.println("</td>");
1321 }
1322 writer.println("</tr>");
1323 }
1324
1325 private void writeOutputRecordField(Field field, Object outputValue,
1326 Class<?> outputClass, PrintWriter writer)
1327 throws IllegalAccessException, InstantiationException {
1328 writer.println("<table>");
1329 Field[] recordFields = outputClass.getDeclaredFields();
1330 for (Field recordField : recordFields) {
1331 Output recordOutputAnnotation = recordField
1332 .getAnnotation(Output.class);
1333 if (null == recordOutputAnnotation) {
1334 continue;
1335 }
1336 writer.println("<tr>");
1337 {
1338 String recordLabel = recordOutputAnnotation.value();
1339 if ("".equals(recordLabel)) {
1340 recordLabel = recordField.getName();
1341 }
1342 new HtmlElement("th").addAttribute("align", "left")
1343 .addAttribute("style", "background-color: #e0e0e0;")
1344 .setBody(recordLabel + ":").write(writer);
1345 recordField.setAccessible(true);
1346 Object value = recordField.get(outputValue);
1347 Class<? extends Renderer<?>> rendererClass = typeRenderers
1348 .get(recordField.getType());
1349 Renderer renderer = rendererClass.newInstance();
1350 writer.println("<td>");
1351 renderer.renderOutput(field.getName() + "."
1352 + recordField.getName(), value, writer);
1353 writer.println("</td>");
1354 }
1355 writer.println("</tr>");
1356 }
1357 writer.println("</table>");
1358 }
1359
1360 private static final Map<Class<?>, Class<? extends Renderer<?>>> typeRenderers = new HashMap<Class<?>, Class<? extends Renderer<?>>>();
1361
1362 static {
1363 typeRenderers.put(Date.class, DateRenderer.class);
1364 typeRenderers.put(Boolean.TYPE, BooleanRenderer.class);
1365 typeRenderers.put(String.class, StringRenderer.class);
1366 typeRenderers.put(Double.TYPE, DoubleRenderer.class);
1367 typeRenderers.put(Integer.TYPE, IntegerRenderer.class);
1368 typeRenderers.put(Float.TYPE, FloatRenderer.class);
1369 }
1370
1371 private void writeConstraintViolation(String constraintViolation,
1372 String fieldName, PrintWriter writer) {
1373 if (null == constraintViolation) {
1374 return;
1375 }
1376 new HtmlElement("div")
1377 .addAttribute("id", "bimbo.error." + fieldName)
1378 .addAttribute(
1379 "style",
1380 "color: red; background-color: #ffe0e0; display: inline; border-style: solid; border-width: 1px; border-color: red; padding-left: 5px; padding-right: 5px;")
1381 .setBody(constraintViolation).write(writer);
1382 }
1383
1384 private void writeInputField(Field field, Input inputAnnotation,
1385 Object pageObject, String constraintViolation, PrintWriter writer) {
1386 String fieldName = field.getName();
1387 writer.println("<tr>");
1388 {
1389 writer
1390 .println("<th style=\"background-color: #e0e0e0;\" align=\"left\">");
1391 {
1392 String inputLabel = inputAnnotation.value();
1393 if ("".equals(inputLabel)) {
1394 inputLabel = fieldName;
1395 }
1396 writer.println(escape(inputLabel) + ":");
1397 Constraint constraintAnnotation = field
1398 .getAnnotation(Constraint.class);
1399 if (null != constraintAnnotation) {
1400 if (constraintAnnotation.required()) {
1401 writer.println("*");
1402 }
1403 }
1404 }
1405 writer.println("</th>");
1406
1407 writer.println("<td>");
1408 writeInputFieldValue(field, inputAnnotation, pageObject, fieldName,
1409 writer);
1410 writeConstraintViolation(constraintViolation, fieldName, writer);
1411 writer.println("</td>");
1412 }
1413 writer.println("</tr>");
1414 }
1415
1416 private void writeInputFieldValue(Field field, Input inputAnnotation,
1417 Object pageObject, String fieldName, PrintWriter writer) {
1418 Object initialValue;
1419 field.setAccessible(true);
1420 try {
1421 initialValue = field.get(pageObject);
1422 } catch (Exception e) {
1423 LOG.debug("error reading field: " + field.getName());
1424 return;
1425 }
1426 Render renderAnnotation = field.getAnnotation(Render.class);
1427 if (null != renderAnnotation) {
1428 Class<? extends Renderer<?>> rendererClass = renderAnnotation
1429 .value();
1430 Renderer renderer;
1431 try {
1432 renderer = rendererClass.newInstance();
1433 } catch (Exception e) {
1434 writer.println("could not init renderer class: "
1435 + rendererClass.getName());
1436 return;
1437 }
1438 renderer.renderInput(fieldName, initialValue, inputAnnotation,
1439 writer);
1440 return;
1441 }
1442 if (typeRenderers.containsKey(field.getType())) {
1443 Class<? extends Renderer<?>> rendererClass = typeRenderers
1444 .get(field.getType());
1445 Renderer renderer;
1446 try {
1447 renderer = rendererClass.newInstance();
1448 } catch (Exception e) {
1449 writer.println("could not init render class: "
1450 + rendererClass.getName());
1451 return;
1452 }
1453 renderer.renderInput(fieldName, initialValue, inputAnnotation,
1454 writer);
1455 return;
1456 }
1457 if (field.getType().isEnum()) {
1458 writer.println("<select name=\"" + fieldName + "\">");
1459 Object[] enumConstants = field.getType().getEnumConstants();
1460 Enum<?> initialEnum = (Enum<?>) initialValue;
1461 for (Object enumConstant : enumConstants) {
1462 Enum<?> enumClass = (Enum<?>) enumConstant;
1463 HtmlElement optionElement = new HtmlElement("option")
1464 .addAttribute("value", enumClass.name()).setBody(
1465 enumConstant.toString());
1466 if (enumClass == initialEnum) {
1467 optionElement.addAttribute("selected", "true");
1468 }
1469 optionElement.write(writer);
1470 }
1471 writer.println("</select>");
1472 return;
1473 }
1474 if (hasOutputFields(field.getType())) {
1475 try {
1476 writeInputRecordField(field, initialValue, writer);
1477 } catch (Exception e) {
1478 LOG.debug("error writing input record field: "
1479 + field.getName(), e);
1480 writer
1481 .println("error writing input record: "
1482 + field.getName());
1483 }
1484 return;
1485 }
1486 writer.println("Could not render input field: " + fieldName);
1487 }
1488
1489 private void writeInputRecordField(Field field, Object outputValue,
1490 PrintWriter writer) throws IllegalArgumentException,
1491 IllegalAccessException, InstantiationException {
1492 writer.println("<table>");
1493 Class<?> outputClass = field.getType();
1494 Field[] recordFields = outputClass.getDeclaredFields();
1495 for (Field recordField : recordFields) {
1496 Output recordOutputAnnotation = recordField
1497 .getAnnotation(Output.class);
1498 if (null == recordOutputAnnotation) {
1499 continue;
1500 }
1501 writer.println("<tr>");
1502 {
1503 String recordLabel = recordOutputAnnotation.value();
1504 if ("".equals(recordLabel)) {
1505 recordLabel = recordField.getName();
1506 }
1507 new HtmlElement("th").addAttribute("align", "left")
1508 .addAttribute("style", "background-color: #e0e0e0;")
1509 .setBody(recordLabel + ":").write(writer);
1510 recordField.setAccessible(true);
1511 Object value;
1512 if (null != outputValue) {
1513 value = recordField.get(outputValue);
1514 } else {
1515 value = null;
1516 }
1517 Class<? extends Renderer<?>> rendererClass = typeRenderers
1518 .get(recordField.getType());
1519 Renderer renderer = rendererClass.newInstance();
1520 writer.println("<td>");
1521 renderer.renderInput(field.getName() + "."
1522 + recordField.getName(), value, null, writer);
1523 writer.println("</td>");
1524 }
1525 writer.println("</tr>");
1526 }
1527 writer.println("</table>");
1528 }
1529
1530 private boolean hasOutputFields(Class<?> outputClass) {
1531 Field[] fields = outputClass.getDeclaredFields();
1532 for (Field field : fields) {
1533 if (null != field.getAnnotation(Output.class)) {
1534 return true;
1535 }
1536 }
1537 return false;
1538 }
1539
1540 private boolean hasFields(Class<?> type) {
1541 if (String.class.equals(type)) {
1542 return false;
1543 }
1544 Field[] fields = type.getDeclaredFields();
1545 return fields.length != 0;
1546 }
1547
1548 private void outputTable(String tableName, List<?> list,
1549 Class<?> pageClass, PrintWriter writer) {
1550 if (null == list) {
1551 return;
1552 }
1553 if (list.isEmpty()) {
1554 return;
1555 }
1556 writer.println("<table style=\"border: 1 px;\">");
1557 Class<?> listEntryClass = list.get(0).getClass();
1558 writer.println("<input type=\"hidden\" name=\"" + tableName
1559 + ".type\" value=\"" + listEntryClass.getName() + "\"/>");
1560 if (hasFields(listEntryClass)) {
1561 writer.println("<tr>");
1562 {
1563 Field[] fields = listEntryClass.getDeclaredFields();
1564 for (Field field : fields) {
1565 Output outputAnnotation = field.getAnnotation(Output.class);
1566 String label;
1567 if (null != outputAnnotation) {
1568 label = outputAnnotation.value();
1569 } else {
1570 label = field.getName();
1571 }
1572 new HtmlElement("th").addAttribute("style",
1573 "background-color: #a0a0a0;").setBody(label).write(
1574 writer);
1575 }
1576 }
1577 writer.println("</tr>");
1578 }
1579 int size = list.size();
1580 for (int idx = 0; idx < size; idx++) {
1581 Object item = list.get(idx);
1582 writer.println("<tr>");
1583 {
1584 {
1585 if (hasFields(listEntryClass)) {
1586 writer.println("<input type=\"hidden\" name=\""
1587 + tableName + "." + idx + "\" value=\""
1588 + listEntryClass.getName() + "\" />");
1589 Field[] fields = listEntryClass.getDeclaredFields();
1590 for (Field field : fields) {
1591 Object fieldValue;
1592 try {
1593 field.setAccessible(true);
1594 fieldValue = field.get(item);
1595 } catch (Exception e) {
1596 writer.println("Could not read field: "
1597 + field.getName());
1598 continue;
1599 }
1600 HtmlElement tdElement = new HtmlElement("td")
1601 .setBody(fieldValue.toString());
1602 if (0 != idx % 2) {
1603 tdElement.addAttribute("style",
1604 "background-color: #e0e0e0;");
1605 }
1606 tdElement.write(writer);
1607 new HtmlElement("input").addAttribute("type",
1608 "hidden").addAttribute(
1609 "name",
1610 tableName + "." + idx + "."
1611 + field.getName()).addAttribute(
1612 "value", fieldValue.toString()).write(
1613 writer);
1614 }
1615 } else {
1616 new HtmlElement("td").setBody(item.toString()).write(
1617 writer);
1618 new HtmlElement("input").addAttribute("type", "hidden")
1619 .addAttribute("name", tableName + "." + idx)
1620 .addAttribute("value", item.toString()).write(
1621 writer);
1622 }
1623 }
1624 }
1625 Method[] methods = pageClass.getDeclaredMethods();
1626 for (Method method : methods) {
1627 Action actionAnnotation = method.getAnnotation(Action.class);
1628 if (null == actionAnnotation) {
1629 continue;
1630 }
1631 if (1 != method.getParameterTypes().length) {
1632 continue;
1633 }
1634
1635
1636
1637 Class<?> methodParamType = method.getParameterTypes()[0];
1638 if (false == methodParamType.equals(listEntryClass)) {
1639 continue;
1640 }
1641 String actionName = actionAnnotation.value();
1642 if ("".equals(actionName)) {
1643 actionName = method.getName();
1644 }
1645 writer.println("<td>");
1646 {
1647 new HtmlElement("input").addAttribute("type", "button")
1648 .addAttribute("value", actionName).addAttribute(
1649 "name",
1650 tableName + "." + method.getName() + "."
1651 + idx).addAttribute(
1652 "onclick",
1653 "doAction('" + method.getName() + "("
1654 + tableName + "." + idx + ")"
1655 + "')").write(writer);
1656 }
1657 writer.println("</td>");
1658 }
1659 writer.println("</tr>");
1660 }
1661 writer.println("</table>");
1662 }
1663
1664 private String getTitle(Class<?> pageClass) {
1665 Title titleAnnotation = pageClass.getAnnotation(Title.class);
1666 if (null == titleAnnotation) {
1667 return pageClass.getSimpleName();
1668 }
1669 return titleAnnotation.value();
1670 }
1671 }