1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.kuali.rice.krad.uif.freemarker;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.lang.StringEscapeUtils;
26  import org.kuali.rice.krad.uif.UifConstants;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.component.ComponentBase;
29  import org.kuali.rice.krad.uif.container.CollectionGroup;
30  import org.kuali.rice.krad.uif.container.Group;
31  import org.kuali.rice.krad.uif.layout.LayoutManager;
32  import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
33  import org.kuali.rice.krad.uif.widget.Disclosure;
34  import org.kuali.rice.krad.uif.widget.Pager;
35  import org.kuali.rice.krad.uif.widget.Tooltip;
36  import org.springframework.util.StringUtils;
37  
38  import freemarker.core.Environment;
39  import freemarker.core.InlineTemplateUtils;
40  import freemarker.core.Macro;
41  import freemarker.ext.beans.BeansWrapper;
42  import freemarker.template.ObjectWrapper;
43  import freemarker.template.TemplateException;
44  import freemarker.template.TemplateModel;
45  import freemarker.template.TemplateModelException;
46  
47  
48  
49  
50  
51  
52  public class FreeMarkerInlineRenderUtils {
53  
54      
55  
56  
57  
58  
59  
60  
61  
62  
63      @SuppressWarnings("unchecked")
64      public static <T> T resolve(Environment env, String name) {
65          TemplateModel tm = resolveModel(env, name);
66          try {
67              return (T) getBeansWrapper(env).unwrap(tm);
68          } catch (TemplateModelException e) {
69              throw new IllegalArgumentException("Failed to unwrap " + name + ", template model " + tm, e);
70          }
71      }
72  
73      
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85      public static <T> T resolve(Environment env, String name, Class<T> type) {
86          Object rv = resolve(env, name);
87  
88          if ((rv instanceof Collection) && !Collection.class.isAssignableFrom(type)) {
89              Collection<?> rc = (Collection<?>) rv;
90              if (rc.isEmpty()) {
91                  return null;
92              } else {
93                  rv = rc.iterator().next();
94              }
95          }
96  
97          if ("".equals(rv) && !String.class.equals(type)) {
98              return null;
99          } else {
100             return type.cast(rv);
101         }
102     }
103 
104     
105 
106 
107 
108 
109 
110 
111     public static BeansWrapper getBeansWrapper(Environment env) {
112         ObjectWrapper wrapper = env.getObjectWrapper();
113 
114         if (!(wrapper instanceof BeansWrapper)) {
115             throw new UnsupportedOperationException("FreeMarker environment uses unsupported ObjectWrapper " + wrapper);
116         }
117 
118         return (BeansWrapper) wrapper;
119     }
120 
121     
122 
123 
124 
125 
126 
127 
128 
129 
130     public static TemplateModel resolveModel(Environment env, String name) {
131         try {
132             return env.getVariable(name);
133         } catch (TemplateModelException e) {
134             throw new IllegalArgumentException("Failed to resolve " + name + " in current freemarker environment", e);
135         }
136     }
137 
138     
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155     public static void renderTemplate(Environment env, Component component, String body,
156             boolean componentUpdate, boolean includeSrc, Map<String, TemplateModel> tmplParms)
157             throws TemplateException, IOException {
158 
159         if (component == null) {
160             return;
161         }
162 
163         String s;
164         Writer out = env.getOut();
165         if ((component.isRender() && (!component.isRetrieveViaAjax() || componentUpdate))
166                 ||
167                 (component.getProgressiveRender() != null && !component.getProgressiveRender().equals("")
168                         && !component.isProgressiveRenderViaAJAX() && !component.isProgressiveRenderAndRefresh())) {
169 
170             if (StringUtils.hasText(s = component.getPreRenderContent())) {
171                 out.write(StringEscapeUtils.escapeHtml(s));
172             }
173 
174             if (component.isSelfRendered()) {
175                 out.write(component.getRenderedHtmlOutput());
176             } else {
177                 if (includeSrc) {
178                     env.include(component.getTemplate(), env.getTemplate().getEncoding(), true);
179                 }
180 
181                 Macro fmMacro = (Macro) env.getMainNamespace().get(component.getTemplateName());
182 
183                 if (fmMacro == null) {
184                     throw new TemplateException("No macro found using " + component.getTemplateName(), env);
185                 }
186 
187                 Map<String, Object> args = new java.util.HashMap<String, Object>();
188                 args.put(component.getComponentTypeName(), component);
189 
190                 if (tmplParms != null) {
191                     args.putAll(tmplParms);
192                 }
193 
194                 if (StringUtils.hasText(body)) {
195                     args.put("body", body);
196                 }
197 
198                 InlineTemplateUtils.invokeMacro(env, fmMacro, args, null);
199             }
200 
201             if (StringUtils.hasText(s = component.getEventHandlerScript())) {
202                 renderScript(s, component, null, out);
203             }
204 
205             if (StringUtils.hasText(s = component.getPostRenderContent())) {
206                 out.append(StringEscapeUtils.escapeHtml(s));
207             }
208 
209         }
210 
211         if (componentUpdate) {
212             return;
213         }
214 
215         String methodToCallOnRefresh = ((ComponentBase) component).getMethodToCallOnRefresh();
216         if (!StringUtils.hasText(methodToCallOnRefresh)) {
217             methodToCallOnRefresh = "";
218         }
219 
220         if (StringUtils.hasText(s = component.getProgressiveRender())) {
221             if (!component.isRender()
222                     && (component.isProgressiveRenderViaAJAX() || component.isProgressiveRenderAndRefresh())) {
223                 out.write("<span id=\"");
224                 out.write(component.getId());
225                 out.write("\" data-role=\"placeholder\" class=\"uif-placeholder\"></span>");
226             }
227 
228             for (String cName : component.getProgressiveDisclosureControlNames()) {
229                 renderScript(
230                         "var condition = function(){return ("
231                                 + component.getProgressiveDisclosureConditionJs()
232                                 + ");};setupProgressiveCheck('" + StringEscapeUtils.escapeJavaScript(cName)
233                                 + "', '" + component.getId() + "', '" + component.getBaseId() + "', condition,"
234                                 + component.isProgressiveRenderAndRefresh() + ", '"
235                                 + methodToCallOnRefresh + "');"
236                         , component, null, out);
237             }
238 
239             renderScript("hiddenInputValidationToggle('" + component.getId() + "');", null, null, out);
240         }
241 
242         if ((component.isProgressiveRenderViaAJAX() && !StringUtils.hasLength(component.getProgressiveRender())) ||
243                 (!component.isRender() && (component.isDisclosedByAction() || component.isRefreshedByAction())) ||
244                 component.isRetrieveViaAjax()) {
245             out.write("<span id=\"");
246             out.write(component.getId());
247             out.write("\" data-role=\"placeholder\" class=\"uif-placeholder\"></span>");
248         }
249 
250         if (StringUtils.hasText(component.getConditionalRefresh())) {
251             for (String cName : component.getConditionalRefreshControlNames()) {
252                 renderScript(
253                         "var condition = function(){return ("
254                                 + component.getConditionalRefreshConditionJs()
255                                 + ");};setupRefreshCheck('" + StringEscapeUtils.escapeJavaScript(cName) + "', '"
256                                 + component.getId() + "', condition,'"
257                                 + methodToCallOnRefresh + "');", null, null, out);
258             }
259         }
260 
261         List<String> refreshWhenChanged = component.getRefreshWhenChangedPropertyNames();
262         if (refreshWhenChanged != null) {
263             for (String cName : refreshWhenChanged) {
264                 renderScript(
265                         "setupOnChangeRefresh('" + StringEscapeUtils.escapeJavaScript(cName) + "', '"
266                                 + component.getId()
267                                 + "','" + methodToCallOnRefresh + "');", null, null, out);
268             }
269         }
270 
271         renderTooltip(component, out);
272     }
273 
274     
275 
276 
277 
278 
279 
280 
281 
282 
283 
284 
285     public static void renderTooltip(Component component, Writer out) throws IOException {
286         Tooltip tt = component.getToolTip();
287         if (tt != null && StringUtils.hasText(tt.getTooltipContent())) {
288             String templateOptionsJSString = tt.getTemplateOptionsJSString();
289             renderScript("createTooltip('" + component.getId() + "', '" + tt.getTooltipContent() + "', "
290                     + (templateOptionsJSString == null ? "''" : templateOptionsJSString) + ", " + tt.isOnMouseHover()
291                     + ", " + tt.isOnFocus() + ");", component, null, out);
292             renderScript("addAttribute('" + component.getId() + "', 'class', 'uif-tooltip', true);", component, null,
293                     out);
294         }
295     }
296 
297     
298 
299 
300 
301 
302 
303 
304 
305 
306 
307 
308 
309     public static void renderScript(String script, Component component, String role, Writer out) throws IOException {
310         if (script == null || "".equals(script.trim()))
311             return;
312         out.write("<input name=\"script\" type=\"hidden\" data-role=\"");
313         out.write(role == null ? "script" : role);
314         out.write("\" ");
315 
316         if (component != null && component.getId() != null) {
317             out.write("data-for=\"");
318             out.write(component.getId());
319             out.write("\" ");
320         }
321 
322         out.write("value=\"");
323         out.write(StringEscapeUtils.escapeHtml(script));
324         out.write("\" />");
325     }
326 
327     
328 
329 
330 
331 
332 
333 
334 
335 
336 
337 
338 
339     public static void renderAttrBuild(Component component, Writer out) throws IOException {
340         String s;
341         if (component instanceof ComponentBase) {
342             ComponentBase componentBase = (ComponentBase) component;
343             if (StringUtils.hasText(s = componentBase.getStyleClassesAsString())) {
344                 out.write(" class=\"");
345                 out.write(s);
346                 out.write("\"");
347             }
348         }
349 
350         if (StringUtils.hasText(s = component.getStyle())) {
351             out.write(" style=\"");
352             out.write(s);
353             out.write("\"");
354         }
355 
356         if (StringUtils.hasText(s = component.getTitle())) {
357             out.write(" title=\"");
358             out.write(s);
359             out.write("\"");
360         }
361     }
362 
363     
364 
365 
366 
367 
368 
369 
370 
371 
372 
373 
374 
375 
376 
377 
378 
379 
380 
381 
382     public static void renderOpenDiv(Component component, Writer out) throws IOException {
383         out.write("<div id=\"");
384         out.write(component.getId());
385         out.write("\"");
386         renderAttrBuild(component, out);
387         out.write(component.getSimpleDataAttributes());
388         out.write(">");
389     }
390 
391     
392 
393 
394 
395 
396 
397 
398 
399 
400 
401 
402 
403 
404 
405 
406 
407 
408 
409 
410     public static void renderCloseDiv(Writer out) throws IOException {
411         out.write("</div>");
412     }
413 
414     
415 
416 
417 
418 
419 
420 
421 
422 
423 
424 
425 
426 
427 
428 
429 
430 
431 
432 
433 
434     public static void renderOpenGroupWrap(Environment env, Group group) throws IOException, TemplateException {
435         Writer out = env.getOut();
436         renderOpenDiv(group, out);
437         renderTemplate(env, group.getHeader(), null, false, false, null);
438 
439         if (group.isRenderLoading()) {
440             out.write("<div id=\"");
441             out.write(group.getId());
442             out.write("_disclosureContent\" data-role=\"placeholder\"> Loading... </div>");
443         } else {
444             Disclosure disclosure = group.getDisclosure();
445             if (disclosure != null && disclosure.isRender()) {
446                 out.write("<div id=\"");
447                 out.write(group.getId() + UifConstants.IdSuffixes.DISCLOSURE_CONTENT);
448                 out.write("\" data-role=\"disclosureContent\" data-open=\"");
449                 out.write(Boolean.toString(disclosure.isDefaultOpen()));
450                 out.write("\" class=\"uif-disclosureContent\">");
451             }
452             renderTemplate(env, group.getValidationMessages(), null, false, false, null);
453             renderTemplate(env, group.getInstructionalMessage(), null, false, false, null);
454         }
455     }
456 
457     
458 
459 
460 
461 
462 
463 
464 
465 
466 
467 
468 
469 
470 
471 
472 
473 
474 
475 
476 
477     public static void renderCloseGroupWrap(Environment env, Group group) throws IOException, TemplateException {
478         Writer out = env.getOut();
479 
480         boolean renderLoading = group.isRenderLoading();
481         if (!renderLoading) {
482             renderTemplate(env, group.getFooter(), null, false, false, null);
483         }
484 
485         Disclosure disclosure = group.getDisclosure();
486         if (disclosure != null && disclosure.isRender()) {
487             if (!renderLoading) {
488                 out.write("</div>");
489             }
490             Map<String, TemplateModel> tmplParms = new HashMap<String, TemplateModel>();
491             tmplParms.put("parent", env.getObjectWrapper().wrap(group));
492             renderTemplate(env, disclosure, null, false, false, tmplParms);
493         }
494 
495         renderCloseDiv(out);
496     }
497 
498     
499 
500 
501 
502 
503 
504 
505 
506 
507 
508 
509 
510 
511     public static void renderCollectionGroup(Environment env, CollectionGroup group) throws IOException,
512             TemplateException {
513         renderOpenGroupWrap(env, group);
514 
515         Map<String, TemplateModel> tmplParms = new HashMap<String, TemplateModel>();
516         tmplParms.put("componentId", env.getObjectWrapper().wrap(group.getId()));
517         renderTemplate(env, group.getCollectionLookup(), null, false, false, tmplParms);
518 
519         if ("TOP".equals(group.getAddLinePlacement())) {
520             if (group.isRenderAddBlankLineButton()) {
521                 renderTemplate(env, group.getAddBlankLineAction(), null, false, false, null);
522             }
523 
524             if (group.isAddViaLightBox()) {
525                 renderTemplate(env, group.getAddViaLightBoxAction(), null, false, false, null);
526             }
527         }
528 
529         LayoutManager layoutManager = group.getLayoutManager();
530         String managerTemplateName = layoutManager.getTemplateName();
531         List<? extends Component> items = group.getItems();
532 
533         if ("uif_stacked".equals(managerTemplateName)) {
534             renderStacked(env, items, (StackedLayoutManager) layoutManager, group);
535         } else {
536             Macro fmMacro = (Macro) env.getMainNamespace().get(layoutManager.getTemplateName());
537 
538             if (fmMacro == null) {
539                 throw new TemplateException("No macro found using " + layoutManager.getTemplateName(), env);
540             }
541 
542             Map<String, Object> args = new java.util.HashMap<String, Object>();
543             args.put("items", items);
544             args.put("manager", group.getLayoutManager());
545             args.put("container", group);
546             InlineTemplateUtils.invokeMacro(env, fmMacro, args, null);
547         }
548 
549         if ("BOTTOM".equals(group.getAddLinePlacement())) {
550             if (group.isRenderAddBlankLineButton()) {
551                 renderTemplate(env, group.getAddBlankLineAction(), null, false, false, null);
552             }
553 
554             if (group.isAddViaLightBox()) {
555                 renderTemplate(env, group.getAddViaLightBoxAction(), null, false, false, null);
556             }
557         }
558 
559         renderCloseGroupWrap(env, group);
560     }
561 
562     
563 
564 
565 
566 
567 
568 
569 
570 
571 
572 
573 
574 
575     public static void renderStacked(Environment env, List<? extends Component> items, StackedLayoutManager manager,
576             CollectionGroup container) throws IOException, TemplateException {
577         String s;
578         Writer out = env.getOut();
579 
580         Pager pager = manager.getPagerWidget();
581         Map<String, TemplateModel> pagerTmplParms = null;
582         if (pager != null && container.isUseServerPaging()) {
583             pagerTmplParms = new HashMap<String, TemplateModel>();
584             pagerTmplParms.put("parent", env.getObjectWrapper().wrap(container));
585             renderTemplate(env, pager, null, false, false, pagerTmplParms);
586         }
587 
588         out.write("<div id=\"");
589         out.write(manager.getId());
590         out.write("\"");
591 
592         if (StringUtils.hasText(s = manager.getStyle())) {
593             out.write(" style=\"");
594             out.write(s);
595             out.write("\"");
596         }
597 
598         if (StringUtils.hasText(s = manager.getStyleClassesAsString())) {
599             out.write(" class=\"");
600             out.write(s);
601             out.write("\"");
602         }
603 
604         out.write(">");
605 
606         Group wrapperGroup = manager.getWrapperGroup();
607         if (wrapperGroup != null) {
608             renderTemplate(env, wrapperGroup, null, false, false, null);
609         } else {
610             for (Group item : manager.getStackedGroups()) {
611                 renderTemplate(env, item, null, false, false, null);
612             }
613         }
614 
615         out.write("</div>");
616 
617         if (pager != null && container.isUseServerPaging()) {
618             pagerTmplParms = new HashMap<String, TemplateModel>();
619             pagerTmplParms.put("parent", env.getObjectWrapper().wrap(container));
620             renderTemplate(env, pager, null, false, false, pagerTmplParms);
621         }
622     }
623 
624 }