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