001
014
015 package com.liferay.portlet.dynamicdatamapping.render;
016
017 import com.liferay.portal.kernel.exception.PortalException;
018 import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
019 import com.liferay.portal.kernel.language.LanguageUtil;
020 import com.liferay.portal.kernel.servlet.JSPSupportServlet;
021 import com.liferay.portal.kernel.template.Template;
022 import com.liferay.portal.kernel.template.TemplateConstants;
023 import com.liferay.portal.kernel.template.TemplateManagerUtil;
024 import com.liferay.portal.kernel.template.TemplateResource;
025 import com.liferay.portal.kernel.template.URLTemplateResource;
026 import com.liferay.portal.kernel.util.ArrayUtil;
027 import com.liferay.portal.kernel.util.CharPool;
028 import com.liferay.portal.kernel.util.GetterUtil;
029 import com.liferay.portal.kernel.util.ParamUtil;
030 import com.liferay.portal.kernel.util.StringBundler;
031 import com.liferay.portal.kernel.util.StringPool;
032 import com.liferay.portal.kernel.util.StringUtil;
033 import com.liferay.portal.kernel.util.Validator;
034 import com.liferay.portlet.dynamicdatamapping.model.DDMForm;
035 import com.liferay.portlet.dynamicdatamapping.model.DDMFormField;
036 import com.liferay.portlet.dynamicdatamapping.model.DDMFormFieldOptions;
037 import com.liferay.portlet.dynamicdatamapping.model.DDMTemplateConstants;
038 import com.liferay.portlet.dynamicdatamapping.model.LocalizedValue;
039 import com.liferay.portlet.dynamicdatamapping.storage.Field;
040 import com.liferay.portlet.dynamicdatamapping.storage.Fields;
041 import com.liferay.portlet.dynamicdatamapping.util.DDMFieldsCounter;
042 import com.liferay.portlet.dynamicdatamapping.util.DDMImpl;
043 import com.liferay.util.freemarker.FreeMarkerTaglibFactoryUtil;
044
045 import freemarker.ext.servlet.HttpRequestHashModel;
046 import freemarker.ext.servlet.ServletContextHashModel;
047
048 import freemarker.template.ObjectWrapper;
049 import freemarker.template.TemplateHashModel;
050
051 import java.io.Writer;
052
053 import java.net.URL;
054
055 import java.util.ArrayList;
056 import java.util.HashMap;
057 import java.util.List;
058 import java.util.Locale;
059 import java.util.Map;
060 import java.util.Set;
061
062 import javax.servlet.GenericServlet;
063 import javax.servlet.http.HttpServletRequest;
064 import javax.servlet.http.HttpServletResponse;
065
066
069 public class DDMFormFieldFreeMarkerRenderer implements DDMFormFieldRenderer {
070
071 public DDMFormFieldFreeMarkerRenderer() {
072 String defaultTemplateId = _TPL_PATH + "alloy/text.ftl";
073
074 URL defaultTemplateURL = getResource(defaultTemplateId);
075
076 _defaultTemplateResource = new URLTemplateResource(
077 defaultTemplateId, defaultTemplateURL);
078
079 String defaultReadOnlyTemplateId = _TPL_PATH + "readonly/default.ftl";
080
081 URL defaultReadOnlyTemplateURL = getResource(defaultReadOnlyTemplateId);
082
083 _defaultReadOnlyTemplateResource = new URLTemplateResource(
084 defaultReadOnlyTemplateId, defaultReadOnlyTemplateURL);
085 }
086
087 @Override
088 public String[] getSupportedDDMFormFieldTypes() {
089 return _SUPPORTED_DDM_FORM_FIELD_TYPES;
090 }
091
092 @Override
093 public String render(
094 DDMFormField ddmFormField,
095 DDMFormFieldRenderingContext ddmFormFieldRenderingContext)
096 throws PortalException {
097
098 try {
099 HttpServletRequest request =
100 ddmFormFieldRenderingContext.getHttpServletRequest();
101 HttpServletResponse response =
102 ddmFormFieldRenderingContext.getHttpServletResponse();
103 Fields fields = ddmFormFieldRenderingContext.getFields();
104 String portletNamespace =
105 ddmFormFieldRenderingContext.getPortletNamespace();
106 String namespace = ddmFormFieldRenderingContext.getNamespace();
107 String mode = ddmFormFieldRenderingContext.getMode();
108 boolean readOnly = ddmFormFieldRenderingContext.isReadOnly();
109 boolean showEmptyFieldLabel =
110 ddmFormFieldRenderingContext.isShowEmptyFieldLabel();
111 Locale locale = ddmFormFieldRenderingContext.getLocale();
112
113 return getFieldHTML(
114 request, response, ddmFormField, fields, null, portletNamespace,
115 namespace, mode, readOnly, showEmptyFieldLabel, locale);
116 }
117 catch (Exception e) {
118 throw new PortalException(e);
119 }
120 }
121
122 protected void addLayoutProperties(
123 DDMFormField ddmFormField, Map<String, Object> fieldContext,
124 Locale locale) {
125
126 LocalizedValue label = ddmFormField.getLabel();
127
128 fieldContext.put("label", label.getString(locale));
129
130 LocalizedValue predefinedValue = ddmFormField.getPredefinedValue();
131
132 fieldContext.put("predefinedValue", predefinedValue.getString(locale));
133
134 LocalizedValue style = ddmFormField.getStyle();
135
136 fieldContext.put("style", style.getString(locale));
137
138 LocalizedValue tip = ddmFormField.getTip();
139
140 fieldContext.put("tip", tip.getString(locale));
141 }
142
143 protected void addStructureProperties(
144 DDMFormField ddmFormField, Map<String, Object> fieldContext) {
145
146 fieldContext.put("dataType", ddmFormField.getDataType());
147 fieldContext.put("fieldNamespace", ddmFormField.getNamespace());
148 fieldContext.put("indexType", ddmFormField.getIndexType());
149 fieldContext.put(
150 "localizable", Boolean.toString(ddmFormField.isLocalizable()));
151 fieldContext.put(
152 "multiple", Boolean.toString(ddmFormField.isMultiple()));
153 fieldContext.put("name", ddmFormField.getName());
154 fieldContext.put(
155 "readOnly", Boolean.toString(ddmFormField.isReadOnly()));
156 fieldContext.put(
157 "repeatable", Boolean.toString(ddmFormField.isRepeatable()));
158 fieldContext.put(
159 "required", Boolean.toString(ddmFormField.isRequired()));
160 fieldContext.put(
161 "showLabel", Boolean.toString(ddmFormField.isShowLabel()));
162 fieldContext.put("type", ddmFormField.getType());
163 }
164
165 protected int countFieldRepetition(
166 String[] fieldsDisplayValues, String parentFieldName, int offset) {
167
168 int total = 0;
169
170 String fieldName = fieldsDisplayValues[offset];
171
172 for (; offset < fieldsDisplayValues.length; offset++) {
173 String fieldNameValue = fieldsDisplayValues[offset];
174
175 if (fieldNameValue.equals(fieldName)) {
176 total++;
177 }
178
179 if (fieldNameValue.equals(parentFieldName)) {
180 break;
181 }
182 }
183
184 return total;
185 }
186
187 protected String getDDMFormFieldOptionHTML(
188 HttpServletRequest request, HttpServletResponse response,
189 DDMFormField ddmFormField, String mode, boolean readOnly,
190 Locale locale, Map<String, Object> freeMarkerContext)
191 throws Exception {
192
193 StringBundler sb = new StringBundler();
194
195 DDMFormFieldOptions ddmFormFieldOptions =
196 ddmFormField.getDDMFormFieldOptions();
197
198 for (String value : ddmFormFieldOptions.getOptionsValues()) {
199 Map<String, Object> fieldStructure = new HashMap<String, Object>();
200
201 fieldStructure.put("children", StringPool.BLANK);
202 fieldStructure.put("fieldNamespace", StringUtil.randomId());
203
204 LocalizedValue label = ddmFormFieldOptions.getOptionLabels(value);
205
206 fieldStructure.put("label", label.getString(locale));
207
208 fieldStructure.put("name", StringUtil.randomId());
209 fieldStructure.put("value", value);
210
211 freeMarkerContext.put("fieldStructure", fieldStructure);
212
213 sb.append(
214 processFTL(
215 request, response, ddmFormField.getNamespace(), "option",
216 mode, readOnly, freeMarkerContext));
217 }
218
219 return sb.toString();
220 }
221
222 protected Map<String, Object> getFieldContext(
223 HttpServletRequest request, HttpServletResponse response,
224 String portletNamespace, String namespace, DDMFormField ddmFormField,
225 Locale locale) {
226
227 Map<String, Map<String, Object>> fieldsContext = getFieldsContext(
228 request, response, portletNamespace, namespace);
229
230 String name = ddmFormField.getName();
231
232 Map<String, Object> fieldContext = fieldsContext.get(name);
233
234 if (fieldContext != null) {
235 return fieldContext;
236 }
237
238 DDMForm ddmForm = ddmFormField.getDDMForm();
239
240 Set<Locale> availableLocales = ddmForm.getAvailableLocales();
241
242 Locale defaultLocale = ddmForm.getDefaultLocale();
243
244 Locale structureLocale = locale;
245
246 if (!availableLocales.contains(locale)) {
247 structureLocale = defaultLocale;
248 }
249
250 fieldContext = new HashMap<String, Object>();
251
252 addLayoutProperties(ddmFormField, fieldContext, structureLocale);
253
254 addStructureProperties(ddmFormField, fieldContext);
255
256 boolean localizable = ddmFormField.isLocalizable();
257
258 if (!localizable && !locale.equals(defaultLocale)) {
259 fieldContext.put("disabled", Boolean.TRUE.toString());
260 }
261
262 boolean checkRequired = GetterUtil.getBoolean(
263 request.getAttribute("checkRequired"), true);
264
265 if (!checkRequired) {
266 fieldContext.put("required", Boolean.FALSE.toString());
267 }
268
269 fieldsContext.put(name, fieldContext);
270
271 return fieldContext;
272 }
273
274 protected String getFieldHTML(
275 HttpServletRequest request, HttpServletResponse response,
276 DDMFormField ddmFormField, Fields fields,
277 DDMFormField parentDDMFormField, String portletNamespace,
278 String namespace, String mode, boolean readOnly,
279 boolean showEmptyFieldLabel, Locale locale)
280 throws Exception {
281
282 Map<String, Object> freeMarkerContext = getFreeMarkerContext(
283 request, response, portletNamespace, namespace, ddmFormField,
284 parentDDMFormField, showEmptyFieldLabel, locale);
285
286 if (fields != null) {
287 freeMarkerContext.put("fields", fields);
288 }
289
290 Map<String, Object> fieldStructure =
291 (Map<String, Object>)freeMarkerContext.get("fieldStructure");
292
293 int fieldRepetition = 1;
294 int offset = 0;
295
296 DDMFieldsCounter ddmFieldsCounter = getFieldsCounter(
297 request, response, fields, portletNamespace, namespace);
298
299 String name = ddmFormField.getName();
300
301 String fieldDisplayValue = getFieldsDisplayValue(
302 request, response, fields);
303
304 String[] fieldsDisplayValues = getFieldsDisplayValues(
305 fieldDisplayValue);
306
307 boolean fieldDisplayable = ArrayUtil.contains(
308 fieldsDisplayValues, name);
309
310 if (fieldDisplayable) {
311 Map<String, Object> parentFieldStructure =
312 (Map<String, Object>)freeMarkerContext.get(
313 "parentFieldStructure");
314
315 String parentFieldName = (String)parentFieldStructure.get("name");
316
317 offset = getFieldOffset(
318 fieldsDisplayValues, name, ddmFieldsCounter.get(name));
319
320 if (offset == fieldsDisplayValues.length) {
321 return StringPool.BLANK;
322 }
323
324 fieldRepetition = countFieldRepetition(
325 fieldsDisplayValues, parentFieldName, offset);
326 }
327
328 StringBundler sb = new StringBundler(fieldRepetition);
329
330 while (fieldRepetition > 0) {
331 offset = getFieldOffset(
332 fieldsDisplayValues, name, ddmFieldsCounter.get(name));
333
334 String fieldNamespace = StringUtil.randomId();
335
336 if (fieldDisplayable) {
337 fieldNamespace = getFieldNamespace(
338 fieldDisplayValue, ddmFieldsCounter, offset);
339 }
340
341 fieldStructure.put("fieldNamespace", fieldNamespace);
342 fieldStructure.put("valueIndex", ddmFieldsCounter.get(name));
343
344 if (fieldDisplayable) {
345 ddmFieldsCounter.incrementKey(name);
346 }
347
348 StringBundler childrenHTML = new StringBundler(2);
349
350 childrenHTML.append(
351 getHTML(
352 request, response, ddmFormField.getNestedDDMFormFields(),
353 fields, ddmFormField, portletNamespace, namespace, mode,
354 readOnly, showEmptyFieldLabel, locale));
355
356 if (Validator.equals(ddmFormField.getType(), "select") ||
357 Validator.equals(ddmFormField.getType(), "radio")) {
358
359 Map<String, Object> optionFreeMarkerContext =
360 new HashMap<String, Object>(freeMarkerContext);
361
362 optionFreeMarkerContext.put(
363 "parentFieldStructure", fieldStructure);
364
365 childrenHTML.append(
366 getDDMFormFieldOptionHTML(
367 request, response, ddmFormField, mode, readOnly, locale,
368 optionFreeMarkerContext));
369 }
370
371 fieldStructure.put("children", childrenHTML.toString());
372
373 boolean disabled = GetterUtil.getBoolean(
374 fieldStructure.get("disabled"), false);
375
376 if (disabled) {
377 readOnly = true;
378 }
379
380 sb.append(
381 processFTL(
382 request, response, ddmFormField.getNamespace(),
383 ddmFormField.getType(), mode, readOnly, freeMarkerContext));
384
385 fieldRepetition--;
386 }
387
388 return sb.toString();
389 }
390
391 protected String getFieldNamespace(
392 String fieldDisplayValue, DDMFieldsCounter ddmFieldsCounter,
393 int offset) {
394
395 String[] fieldsDisplayValues = StringUtil.split(fieldDisplayValue);
396
397 String fieldsDisplayValue = fieldsDisplayValues[offset];
398
399 return StringUtil.extractLast(
400 fieldsDisplayValue, DDMImpl.INSTANCE_SEPARATOR);
401 }
402
403 protected int getFieldOffset(
404 String[] fieldsDisplayValues, String name, int index) {
405
406 int offset = 0;
407
408 for (; offset < fieldsDisplayValues.length; offset++) {
409 if (name.equals(fieldsDisplayValues[offset])) {
410 index--;
411
412 if (index < 0) {
413 break;
414 }
415 }
416 }
417
418 return offset;
419 }
420
421 protected Map<String, Map<String, Object>> getFieldsContext(
422 HttpServletRequest request, HttpServletResponse response,
423 String portletNamespace, String namespace) {
424
425 String fieldsContextKey =
426 portletNamespace + namespace + "fieldsContext";
427
428 Map<String, Map<String, Object>> fieldsContext =
429 (Map<String, Map<String, Object>>)request.getAttribute(
430 fieldsContextKey);
431
432 if (fieldsContext == null) {
433 fieldsContext = new HashMap<String, Map<String, Object>>();
434
435 request.setAttribute(fieldsContextKey, fieldsContext);
436 }
437
438 return fieldsContext;
439 }
440
441 protected DDMFieldsCounter getFieldsCounter(
442 HttpServletRequest request, HttpServletResponse response, Fields fields,
443 String portletNamespace, String namespace) {
444
445 String fieldsCounterKey = portletNamespace + namespace + "fieldsCount";
446
447 DDMFieldsCounter ddmFieldsCounter =
448 (DDMFieldsCounter)request.getAttribute(fieldsCounterKey);
449
450 if (ddmFieldsCounter == null) {
451 ddmFieldsCounter = new DDMFieldsCounter();
452
453 request.setAttribute(fieldsCounterKey, ddmFieldsCounter);
454 }
455
456 return ddmFieldsCounter;
457 }
458
459 protected String getFieldsDisplayValue(
460 HttpServletRequest request, HttpServletResponse response,
461 Fields fields) {
462
463 String defaultFieldsDisplayValue = null;
464
465 if (fields != null) {
466 Field fieldsDisplayField = fields.get(DDMImpl.FIELDS_DISPLAY_NAME);
467
468 if (fieldsDisplayField != null) {
469 defaultFieldsDisplayValue =
470 (String)fieldsDisplayField.getValue();
471 }
472 }
473
474 return ParamUtil.getString(
475 request, DDMImpl.FIELDS_DISPLAY_NAME, defaultFieldsDisplayValue);
476 }
477
478 protected String[] getFieldsDisplayValues(String fieldDisplayValue) {
479 List<String> fieldsDisplayValues = new ArrayList<String>();
480
481 for (String value : StringUtil.split(fieldDisplayValue)) {
482 String fieldName = StringUtil.extractFirst(
483 value, DDMImpl.INSTANCE_SEPARATOR);
484
485 fieldsDisplayValues.add(fieldName);
486 }
487
488 return fieldsDisplayValues.toArray(
489 new String[fieldsDisplayValues.size()]);
490 }
491
492 protected Map<String, Object> getFreeMarkerContext(
493 HttpServletRequest request, HttpServletResponse response,
494 String portletNamespace, String namespace, DDMFormField ddmFormField,
495 DDMFormField parentDDMFormField, boolean showEmptyFieldLabel,
496 Locale locale) {
497
498 Map<String, Object> freeMarkerContext = new HashMap<String, Object>();
499
500 Map<String, Object> fieldContext = getFieldContext(
501 request, response, portletNamespace, namespace, ddmFormField,
502 locale);
503
504 Map<String, Object> parentFieldContext = new HashMap<String, Object>();
505
506 if (parentDDMFormField != null) {
507 parentFieldContext = getFieldContext(
508 request, response, portletNamespace, namespace,
509 parentDDMFormField, locale);
510 }
511
512 freeMarkerContext.put("fieldStructure", fieldContext);
513 freeMarkerContext.put("namespace", namespace);
514 freeMarkerContext.put("parentFieldStructure", parentFieldContext);
515 freeMarkerContext.put("portletNamespace", portletNamespace);
516 freeMarkerContext.put(
517 "requestedLanguageDir", LanguageUtil.get(locale, "lang.dir"));
518 freeMarkerContext.put("requestedLocale", locale);
519 freeMarkerContext.put("showEmptyFieldLabel", showEmptyFieldLabel);
520
521 return freeMarkerContext;
522 }
523
524 protected String getHTML(
525 HttpServletRequest request, HttpServletResponse response,
526 List<DDMFormField> ddmFormFields, Fields fields,
527 DDMFormField parentDDMFormField, String portletNamespace,
528 String namespace, String mode, boolean readOnly,
529 boolean showEmptyFieldLabel, Locale locale)
530 throws Exception {
531
532 StringBundler sb = new StringBundler(ddmFormFields.size());
533
534 for (DDMFormField ddmFormField : ddmFormFields) {
535 sb.append(
536 getFieldHTML(
537 request, response, ddmFormField, fields, parentDDMFormField,
538 portletNamespace, namespace, mode, readOnly,
539 showEmptyFieldLabel, locale));
540 }
541
542 return sb.toString();
543 }
544
545 protected URL getResource(String name) {
546 Class<?> clazz = getClass();
547
548 ClassLoader classLoader = clazz.getClassLoader();
549
550 return classLoader.getResource(name);
551 }
552
553 protected String processFTL(
554 HttpServletRequest request, HttpServletResponse response,
555 String fieldNamespace, String type, String mode, boolean readOnly,
556 Map<String, Object> freeMarkerContext)
557 throws Exception {
558
559 if (Validator.isNull(fieldNamespace)) {
560 fieldNamespace = _DEFAULT_NAMESPACE;
561 }
562
563 TemplateResource templateResource = _defaultTemplateResource;
564
565 Map<String, Object> fieldStructure =
566 (Map<String, Object>)freeMarkerContext.get("fieldStructure");
567
568 boolean fieldReadOnly = GetterUtil.getBoolean(
569 fieldStructure.get("readOnly"));
570
571 if ((fieldReadOnly && Validator.isNotNull(mode) &&
572 StringUtil.equalsIgnoreCase(
573 mode, DDMTemplateConstants.TEMPLATE_MODE_EDIT)) ||
574 readOnly) {
575
576 fieldNamespace = _DEFAULT_READ_ONLY_NAMESPACE;
577
578 templateResource = _defaultReadOnlyTemplateResource;
579 }
580
581 String templateName = StringUtil.replaceFirst(
582 type, fieldNamespace.concat(StringPool.DASH), StringPool.BLANK);
583
584 StringBundler resourcePath = new StringBundler(5);
585
586 resourcePath.append(_TPL_PATH);
587 resourcePath.append(StringUtil.toLowerCase(fieldNamespace));
588 resourcePath.append(CharPool.SLASH);
589 resourcePath.append(templateName);
590 resourcePath.append(_TPL_EXT);
591
592 String resource = resourcePath.toString();
593
594 URL url = getResource(resource);
595
596 if (url != null) {
597 templateResource = new URLTemplateResource(resource, url);
598 }
599
600 if (templateResource == null) {
601 throw new Exception("Unable to load template resource " + resource);
602 }
603
604 Template template = TemplateManagerUtil.getTemplate(
605 TemplateConstants.LANG_TYPE_FTL, templateResource, false);
606
607 for (Map.Entry<String, Object> entry : freeMarkerContext.entrySet()) {
608 template.put(entry.getKey(), entry.getValue());
609 }
610
611 return processFTL(request, response, template);
612 }
613
614
617 protected String processFTL(
618 HttpServletRequest request, HttpServletResponse response,
619 Template template)
620 throws Exception {
621
622
623
624 template.prepare(request);
625
626
627
628 Writer writer = new UnsyncStringWriter();
629
630
631
632 TemplateHashModel portalTaglib =
633 FreeMarkerTaglibFactoryUtil.createTaglibFactory(
634 request.getServletContext());
635
636 template.put("PortalJspTagLibs", portalTaglib);
637
638
639
640 GenericServlet genericServlet = new JSPSupportServlet(
641 request.getServletContext());
642
643 ServletContextHashModel servletContextHashModel =
644 new ServletContextHashModel(
645 genericServlet, ObjectWrapper.DEFAULT_WRAPPER);
646
647 template.put("Application", servletContextHashModel);
648
649 HttpRequestHashModel httpRequestHashModel = new HttpRequestHashModel(
650 request, response, ObjectWrapper.DEFAULT_WRAPPER);
651
652 template.put("Request", httpRequestHashModel);
653
654
655
656 template.processTemplate(writer);
657
658 return writer.toString();
659 }
660
661 private static final String _DEFAULT_NAMESPACE = "alloy";
662
663 private static final String _DEFAULT_READ_ONLY_NAMESPACE = "readonly";
664
665 private static final String[] _SUPPORTED_DDM_FORM_FIELD_TYPES = {
666 "checkbox", "ddm-date", "ddm-decimal", "ddm-documentlibrary",
667 "ddm-geolocation", "ddm-image", "ddm-integer", "ddm-link-to-page",
668 "ddm-number", "ddm-separator", "ddm-text-html", "fieldset", "option",
669 "radio", "select", "text", "textarea"
670 };
671
672 private static final String _TPL_EXT = ".ftl";
673
674 private static final String _TPL_PATH =
675 "com/liferay/portlet/dynamicdatamapping/dependencies/";
676
677 private TemplateResource _defaultReadOnlyTemplateResource;
678 private TemplateResource _defaultTemplateResource;
679
680 }