001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
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    /**
059     * @author Pablo Carvalho
060     */
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            /**
617             * @see com.liferay.taglib.util.ThemeUtil#includeFTL
618             */
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    }