/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.dynamic.data.mapping.internal.render;
import com.liferay.dynamic.data.mapping.constants.DDMPortletKeys;
import com.liferay.dynamic.data.mapping.internal.util.DDMFieldsCounter;
import com.liferay.dynamic.data.mapping.internal.util.DDMImpl;
import com.liferay.dynamic.data.mapping.model.DDMForm;
import com.liferay.dynamic.data.mapping.model.DDMFormField;
import com.liferay.dynamic.data.mapping.model.DDMFormFieldOptions;
import com.liferay.dynamic.data.mapping.model.DDMTemplateConstants;
import com.liferay.dynamic.data.mapping.model.LocalizedValue;
import com.liferay.dynamic.data.mapping.render.DDMFormFieldRenderer;
import com.liferay.dynamic.data.mapping.render.DDMFormFieldRenderingContext;
import com.liferay.dynamic.data.mapping.storage.Field;
import com.liferay.dynamic.data.mapping.storage.Fields;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
import com.liferay.portal.kernel.language.LanguageConstants;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.auth.AuthTokenUtil;
import com.liferay.portal.kernel.template.ClassLoaderTemplateResource;
import com.liferay.portal.kernel.template.Template;
import com.liferay.portal.kernel.template.TemplateConstants;
import com.liferay.portal.kernel.template.TemplateManager;
import com.liferay.portal.kernel.template.TemplateManagerUtil;
import com.liferay.portal.kernel.template.TemplateResource;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.PortletKeys;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.util.WebKeys;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Pablo Carvalho
*/
public class DDMFormFieldFreeMarkerRenderer implements DDMFormFieldRenderer {
public DDMFormFieldFreeMarkerRenderer() {
String defaultTemplateId = _TPL_PATH + "alloy/text.ftl";
_defaultTemplateResource = getTemplateResource(defaultTemplateId);
String defaultReadOnlyTemplateId = _TPL_PATH + "readonly/default.ftl";
_defaultReadOnlyTemplateResource = getTemplateResource(
defaultReadOnlyTemplateId);
}
@Override
public String[] getSupportedDDMFormFieldTypes() {
return _SUPPORTED_DDM_FORM_FIELD_TYPES;
}
@Override
public String render(
DDMFormField ddmFormField,
DDMFormFieldRenderingContext ddmFormFieldRenderingContext)
throws PortalException {
try {
HttpServletRequest request =
ddmFormFieldRenderingContext.getHttpServletRequest();
HttpServletResponse response =
ddmFormFieldRenderingContext.getHttpServletResponse();
Fields fields = ddmFormFieldRenderingContext.getFields();
String portletNamespace =
ddmFormFieldRenderingContext.getPortletNamespace();
String namespace = ddmFormFieldRenderingContext.getNamespace();
String mode = ddmFormFieldRenderingContext.getMode();
boolean readOnly = ddmFormFieldRenderingContext.isReadOnly();
boolean showEmptyFieldLabel =
ddmFormFieldRenderingContext.isShowEmptyFieldLabel();
Locale locale = ddmFormFieldRenderingContext.getLocale();
return getFieldHTML(
request, response, ddmFormField, fields, null, portletNamespace,
namespace, mode, readOnly, showEmptyFieldLabel, locale);
}
catch (Exception e) {
throw new PortalException(e);
}
}
protected void addLayoutProperties(
DDMFormField ddmFormField, Map<String, Object> fieldContext,
Locale locale) {
LocalizedValue label = ddmFormField.getLabel();
fieldContext.put("label", label.getString(locale));
LocalizedValue predefinedValue = ddmFormField.getPredefinedValue();
fieldContext.put("predefinedValue", predefinedValue.getString(locale));
LocalizedValue style = ddmFormField.getStyle();
fieldContext.put("style", style.getString(locale));
LocalizedValue tip = ddmFormField.getTip();
fieldContext.put("tip", tip.getString(locale));
}
protected void addStructureProperties(
DDMFormField ddmFormField, Map<String, Object> fieldContext) {
fieldContext.put("dataType", ddmFormField.getDataType());
fieldContext.put("indexType", ddmFormField.getIndexType());
fieldContext.put(
"localizable", Boolean.toString(ddmFormField.isLocalizable()));
fieldContext.put(
"multiple", Boolean.toString(ddmFormField.isMultiple()));
fieldContext.put("name", ddmFormField.getName());
fieldContext.put(
"readOnly", Boolean.toString(ddmFormField.isReadOnly()));
fieldContext.put(
"repeatable", Boolean.toString(ddmFormField.isRepeatable()));
fieldContext.put(
"required", Boolean.toString(ddmFormField.isRequired()));
fieldContext.put(
"showLabel", Boolean.toString(ddmFormField.isShowLabel()));
fieldContext.put("type", ddmFormField.getType());
}
protected int countFieldRepetition(
String[] fieldsDisplayValues, String parentFieldName, int offset) {
int total = 0;
String fieldName = fieldsDisplayValues[offset];
for (; offset < fieldsDisplayValues.length; offset++) {
String fieldNameValue = fieldsDisplayValues[offset];
if (fieldNameValue.equals(fieldName)) {
total++;
}
if (fieldNameValue.equals(parentFieldName)) {
break;
}
}
return total;
}
protected String getDDMFormFieldOptionHTML(
HttpServletRequest request, HttpServletResponse response,
DDMFormField ddmFormField, String mode, boolean readOnly,
Locale locale, Map<String, Object> freeMarkerContext)
throws Exception {
StringBundler sb = new StringBundler();
DDMFormFieldOptions ddmFormFieldOptions =
ddmFormField.getDDMFormFieldOptions();
for (String value : ddmFormFieldOptions.getOptionsValues()) {
Map<String, Object> fieldStructure = new HashMap<>();
fieldStructure.put("children", StringPool.BLANK);
fieldStructure.put("fieldNamespace", StringUtil.randomId());
LocalizedValue label = ddmFormFieldOptions.getOptionLabels(value);
fieldStructure.put("label", label.getString(locale));
fieldStructure.put("name", StringUtil.randomId());
fieldStructure.put("value", value);
freeMarkerContext.put("fieldStructure", fieldStructure);
sb.append(
processFTL(
request, response, ddmFormField.getFieldNamespace(),
"option", mode, readOnly, freeMarkerContext));
}
return sb.toString();
}
protected Map<String, Object> getFieldContext(
HttpServletRequest request, HttpServletResponse response,
String portletNamespace, String namespace, DDMFormField ddmFormField,
Locale locale) {
Map<String, Map<String, Object>> fieldsContext = getFieldsContext(
request, response, portletNamespace, namespace);
String name = ddmFormField.getName();
Map<String, Object> fieldContext = fieldsContext.get(name);
if (fieldContext != null) {
return fieldContext;
}
DDMForm ddmForm = ddmFormField.getDDMForm();
Set<Locale> availableLocales = ddmForm.getAvailableLocales();
Locale defaultLocale = ddmForm.getDefaultLocale();
Locale structureLocale = locale;
if (!availableLocales.contains(locale)) {
structureLocale = defaultLocale;
}
fieldContext = new HashMap<>();
addLayoutProperties(ddmFormField, fieldContext, structureLocale);
addStructureProperties(ddmFormField, fieldContext);
boolean checkRequired = GetterUtil.getBoolean(
request.getAttribute("checkRequired"), true);
if (!checkRequired) {
fieldContext.put("required", Boolean.FALSE.toString());
}
fieldsContext.put(name, fieldContext);
return fieldContext;
}
protected String getFieldHTML(
HttpServletRequest request, HttpServletResponse response,
DDMFormField ddmFormField, Fields fields,
DDMFormField parentDDMFormField, String portletNamespace,
String namespace, String mode, boolean readOnly,
boolean showEmptyFieldLabel, Locale locale)
throws Exception {
Map<String, Object> freeMarkerContext = getFreeMarkerContext(
request, response, portletNamespace, namespace, ddmFormField,
parentDDMFormField, showEmptyFieldLabel, locale);
if (fields != null) {
freeMarkerContext.put("fields", fields);
}
Map<String, Object> fieldStructure =
(Map<String, Object>)freeMarkerContext.get("fieldStructure");
int fieldRepetition = 1;
int offset = 0;
DDMFieldsCounter ddmFieldsCounter = getFieldsCounter(
request, response, fields, portletNamespace, namespace);
String name = ddmFormField.getName();
String fieldDisplayValue = getFieldsDisplayValue(
request, response, fields);
String[] fieldsDisplayValues = getFieldsDisplayValues(
fieldDisplayValue);
boolean fieldDisplayable = ArrayUtil.contains(
fieldsDisplayValues, name);
if (fieldDisplayable) {
Map<String, Object> parentFieldStructure =
(Map<String, Object>)freeMarkerContext.get(
"parentFieldStructure");
String parentFieldName = (String)parentFieldStructure.get("name");
offset = getFieldOffset(
fieldsDisplayValues, name, ddmFieldsCounter.get(name));
if (offset == fieldsDisplayValues.length) {
return StringPool.BLANK;
}
fieldRepetition = countFieldRepetition(
fieldsDisplayValues, parentFieldName, offset);
}
StringBundler sb = new StringBundler(fieldRepetition);
while (fieldRepetition > 0) {
offset = getFieldOffset(
fieldsDisplayValues, name, ddmFieldsCounter.get(name));
String fieldNamespace = StringUtil.randomId();
if (fieldDisplayable) {
fieldNamespace = getFieldNamespace(
fieldDisplayValue, ddmFieldsCounter, offset);
}
fieldStructure.put("fieldNamespace", fieldNamespace);
fieldStructure.put("valueIndex", ddmFieldsCounter.get(name));
if (fieldDisplayable) {
ddmFieldsCounter.incrementKey(name);
}
StringBundler childrenHTML = new StringBundler(2);
childrenHTML.append(
getHTML(
request, response, ddmFormField.getNestedDDMFormFields(),
fields, ddmFormField, portletNamespace, namespace, mode,
readOnly, showEmptyFieldLabel, locale));
if (Objects.equals(ddmFormField.getType(), "select") ||
Objects.equals(ddmFormField.getType(), "radio")) {
Map<String, Object> optionFreeMarkerContext = new HashMap<>(
freeMarkerContext);
optionFreeMarkerContext.put(
"parentFieldStructure", fieldStructure);
childrenHTML.append(
getDDMFormFieldOptionHTML(
request, response, ddmFormField, mode, readOnly, locale,
optionFreeMarkerContext));
}
fieldStructure.put("children", childrenHTML.toString());
sb.append(
processFTL(
request, response, ddmFormField.getFieldNamespace(),
ddmFormField.getType(), mode, readOnly, freeMarkerContext));
fieldRepetition--;
}
return sb.toString();
}
protected String getFieldNamespace(
String fieldDisplayValue, DDMFieldsCounter ddmFieldsCounter,
int offset) {
String[] fieldsDisplayValues = StringUtil.split(fieldDisplayValue);
String fieldsDisplayValue = fieldsDisplayValues[offset];
return StringUtil.extractLast(
fieldsDisplayValue, DDMImpl.INSTANCE_SEPARATOR);
}
protected int getFieldOffset(
String[] fieldsDisplayValues, String name, int index) {
int offset = 0;
for (; offset < fieldsDisplayValues.length; offset++) {
if (name.equals(fieldsDisplayValues[offset])) {
index--;
if (index < 0) {
break;
}
}
}
return offset;
}
protected Map<String, Map<String, Object>> getFieldsContext(
HttpServletRequest request, HttpServletResponse response,
String portletNamespace, String namespace) {
String fieldsContextKey =
portletNamespace + namespace + "fieldsContext";
Map<String, Map<String, Object>> fieldsContext =
(Map<String, Map<String, Object>>)request.getAttribute(
fieldsContextKey);
if (fieldsContext == null) {
fieldsContext = new HashMap<>();
request.setAttribute(fieldsContextKey, fieldsContext);
}
return fieldsContext;
}
protected DDMFieldsCounter getFieldsCounter(
HttpServletRequest request, HttpServletResponse response, Fields fields,
String portletNamespace, String namespace) {
String fieldsCounterKey = portletNamespace + namespace + "fieldsCount";
DDMFieldsCounter ddmFieldsCounter =
(DDMFieldsCounter)request.getAttribute(fieldsCounterKey);
if (ddmFieldsCounter == null) {
ddmFieldsCounter = new DDMFieldsCounter();
request.setAttribute(fieldsCounterKey, ddmFieldsCounter);
}
return ddmFieldsCounter;
}
protected String getFieldsDisplayValue(
HttpServletRequest request, HttpServletResponse response,
Fields fields) {
String defaultFieldsDisplayValue = null;
if (fields != null) {
Field fieldsDisplayField = fields.get(DDMImpl.FIELDS_DISPLAY_NAME);
if (fieldsDisplayField != null) {
defaultFieldsDisplayValue =
(String)fieldsDisplayField.getValue();
}
}
return ParamUtil.getString(
request, DDMImpl.FIELDS_DISPLAY_NAME, defaultFieldsDisplayValue);
}
protected String[] getFieldsDisplayValues(String fieldDisplayValue) {
List<String> fieldsDisplayValues = new ArrayList<>();
for (String value : StringUtil.split(fieldDisplayValue)) {
String fieldName = StringUtil.extractFirst(
value, DDMImpl.INSTANCE_SEPARATOR);
fieldsDisplayValues.add(fieldName);
}
return fieldsDisplayValues.toArray(
new String[fieldsDisplayValues.size()]);
}
protected Map<String, Object> getFreeMarkerContext(
HttpServletRequest request, HttpServletResponse response,
String portletNamespace, String namespace, DDMFormField ddmFormField,
DDMFormField parentDDMFormField, boolean showEmptyFieldLabel,
Locale locale) {
Map<String, Object> freeMarkerContext = new HashMap<>();
Map<String, Object> fieldContext = getFieldContext(
request, response, portletNamespace, namespace, ddmFormField,
locale);
Map<String, Object> parentFieldContext = new HashMap<>();
if (parentDDMFormField != null) {
parentFieldContext = getFieldContext(
request, response, portletNamespace, namespace,
parentDDMFormField, locale);
}
freeMarkerContext.put(
"ddmPortletId", DDMPortletKeys.DYNAMIC_DATA_MAPPING);
freeMarkerContext.put("fieldStructure", fieldContext);
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);
try {
String itemSelectorAuthToken = AuthTokenUtil.getToken(
request,
PortalUtil.getControlPanelPlid(themeDisplay.getCompanyId()),
PortletKeys.ITEM_SELECTOR);
freeMarkerContext.put(
"itemSelectorAuthToken", itemSelectorAuthToken);
}
catch (PortalException pe) {
_log.error("Unable to generate item selector auth token ", pe);
}
freeMarkerContext.put("namespace", namespace);
freeMarkerContext.put("parentFieldStructure", parentFieldContext);
freeMarkerContext.put("portletNamespace", portletNamespace);
freeMarkerContext.put(
"requestedLanguageDir",
LanguageUtil.get(locale, LanguageConstants.KEY_DIR));
freeMarkerContext.put("requestedLocale", locale);
freeMarkerContext.put("showEmptyFieldLabel", showEmptyFieldLabel);
return freeMarkerContext;
}
protected String getHTML(
HttpServletRequest request, HttpServletResponse response,
List<DDMFormField> ddmFormFields, Fields fields,
DDMFormField parentDDMFormField, String portletNamespace,
String namespace, String mode, boolean readOnly,
boolean showEmptyFieldLabel, Locale locale)
throws Exception {
StringBundler sb = new StringBundler(ddmFormFields.size());
for (DDMFormField ddmFormField : ddmFormFields) {
sb.append(
getFieldHTML(
request, response, ddmFormField, fields, parentDDMFormField,
portletNamespace, namespace, mode, readOnly,
showEmptyFieldLabel, locale));
}
return sb.toString();
}
protected URL getResource(String name) {
Class<?> clazz = getClass();
ClassLoader classLoader = clazz.getClassLoader();
return classLoader.getResource(name);
}
protected TemplateResource getTemplateResource(String resource) {
Class<?> clazz = getClass();
TemplateResource templateResource = new ClassLoaderTemplateResource(
clazz.getClassLoader(), resource);
return templateResource;
}
protected String processFTL(
HttpServletRequest request, HttpServletResponse response,
String fieldNamespace, String type, String mode, boolean readOnly,
Map<String, Object> freeMarkerContext)
throws Exception {
if (Validator.isNull(fieldNamespace)) {
fieldNamespace = _DEFAULT_NAMESPACE;
}
TemplateResource templateResource = _defaultTemplateResource;
Map<String, Object> fieldStructure =
(Map<String, Object>)freeMarkerContext.get("fieldStructure");
boolean fieldReadOnly = GetterUtil.getBoolean(
fieldStructure.get("readOnly"));
if ((fieldReadOnly && Validator.isNotNull(mode) &&
StringUtil.equalsIgnoreCase(
mode, DDMTemplateConstants.TEMPLATE_MODE_EDIT)) ||
readOnly) {
fieldNamespace = _DEFAULT_READ_ONLY_NAMESPACE;
templateResource = _defaultReadOnlyTemplateResource;
}
String templateName = StringUtil.replaceFirst(
type, fieldNamespace.concat(StringPool.DASH), StringPool.BLANK);
StringBundler resourcePath = new StringBundler(5);
resourcePath.append(_TPL_PATH);
resourcePath.append(StringUtil.toLowerCase(fieldNamespace));
resourcePath.append(CharPool.SLASH);
resourcePath.append(templateName);
resourcePath.append(_TPL_EXT);
String resource = resourcePath.toString();
URL url = getResource(resource);
if (url != null) {
templateResource = getTemplateResource(resource);
}
if (templateResource == null) {
throw new Exception("Unable to load template resource " + resource);
}
Template template = TemplateManagerUtil.getTemplate(
TemplateConstants.LANG_TYPE_FTL, templateResource, false);
for (Map.Entry<String, Object> entry : freeMarkerContext.entrySet()) {
template.put(entry.getKey(), entry.getValue());
}
TemplateManager templateManager =
TemplateManagerUtil.getTemplateManager(
TemplateConstants.LANG_TYPE_FTL);
templateManager.addTaglibSupport(template, request, response);
return processFTL(request, response, template);
}
/**
* @see com.liferay.taglib.util.ThemeUtil#includeFTL
*/
protected String processFTL(
HttpServletRequest request, HttpServletResponse response,
Template template)
throws Exception {
template.prepare(request);
Writer writer = new UnsyncStringWriter();
template.processTemplate(writer);
return writer.toString();
}
private static final String _DEFAULT_NAMESPACE = "alloy";
private static final String _DEFAULT_READ_ONLY_NAMESPACE = "readonly";
private static final String[] _SUPPORTED_DDM_FORM_FIELD_TYPES = {
"checkbox", "ddm-color", "ddm-date", "ddm-decimal",
"ddm-documentlibrary", "ddm-geolocation", "ddm-image", "ddm-integer",
"ddm-journal-article", "ddm-link-to-page", "ddm-number",
"ddm-paragraph", "ddm-separator", "ddm-text-html", "fieldset", "option",
"radio", "select", "text", "textarea"
};
private static final String _TPL_EXT = ".ftl";
private static final String _TPL_PATH =
"com/liferay/dynamic/data/mapping/dependencies/";
private static final Log _log = LogFactoryUtil.getLog(
DDMFormFieldFreeMarkerRenderer.class);
private final TemplateResource _defaultReadOnlyTemplateResource;
private final TemplateResource _defaultTemplateResource;
}