001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.containerpage.CmsElementUtil;
032import org.opencms.ade.containerpage.shared.CmsContainerElement;
033import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
034import org.opencms.file.CmsObject;
035import org.opencms.file.CmsResource;
036import org.opencms.file.CmsResourceFilter;
037import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler;
038import org.opencms.flex.CmsFlexController;
039import org.opencms.jsp.util.CmsJspContentAccessValueWrapper;
040import org.opencms.jsp.util.CmsJspStandardContextBean;
041import org.opencms.main.CmsException;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.util.CmsRequestUtil;
045import org.opencms.util.CmsStringUtil;
046import org.opencms.util.CmsUUID;
047import org.opencms.workplace.editors.directedit.CmsAdvancedDirectEditProvider;
048import org.opencms.workplace.editors.directedit.CmsDirectEditMode;
049import org.opencms.workplace.editors.directedit.I_CmsDirectEditProvider;
050import org.opencms.xml.containerpage.CmsADESessionCache;
051import org.opencms.xml.containerpage.CmsContainerElementBean;
052import org.opencms.xml.containerpage.CmsFormatterConfiguration;
053import org.opencms.xml.containerpage.I_CmsFormatterBean;
054import org.opencms.xml.types.CmsXmlDisplayFormatterValue;
055import org.opencms.xml.types.I_CmsXmlContentValue;
056
057import java.util.HashMap;
058import java.util.LinkedHashMap;
059import java.util.List;
060import java.util.Locale;
061import java.util.Map;
062import java.util.Map.Entry;
063
064import javax.servlet.ServletRequest;
065import javax.servlet.ServletResponse;
066import javax.servlet.http.HttpServletRequest;
067import javax.servlet.jsp.JspException;
068import javax.servlet.jsp.PageContext;
069import javax.servlet.jsp.tagext.BodyTagSupport;
070
071import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
072import org.apache.commons.logging.Log;
073
074/**
075 * The 'display' tag can be used to display a single resource using a formatter. It also allows to activate direct editing.<p>
076 */
077public class CmsJspTagDisplay extends BodyTagSupport implements I_CmsJspTagParamParent {
078
079    /** Setting used to store the display formatter key. */
080    public static final String DISPLAY_FORMATTER_SETTING = "SYSTEM::DISPLAY_FORMATTER";
081
082    /** The log object for this class. */
083    private static final Log LOG = CmsLog.getLog(CmsJspTagDisplay.class);
084
085    /** The serial version id. */
086    private static final long serialVersionUID = 2285680951218629093L;
087
088    /** The base URI. */
089    private String m_baseUri;
090
091    /** True if the display formatter include should go through the flex cache. */
092    private Boolean m_cacheable;
093
094    /** Flag, indicating if the create option should be displayed. */
095    private boolean m_canCreate;
096
097    /** Flag, indicating if the delete option should be displayed. */
098    private boolean m_canDelete;
099
100    /** The tag attribute's value, specifying the path to the (sub)sitemap where new content should be created. */
101    private String m_creationSiteMap;
102
103    /** The display formatter ids. */
104    private Map<String, String> m_displayFormatterIds;
105
106    /** The display formatter paths. */
107    private Map<String, String> m_displayFormatterPaths;
108
109    /** The editable flag. */
110    private boolean m_editable;
111
112    /** The settings parameter map. */
113    private Map<String, String> m_parameterMap;
114
115    /** The pass settings flag. */
116    private boolean m_passSettings;
117
118    /** The fully qualified class name of the post create handler to use. */
119    private String m_postCreateHandler;
120
121    /** The element settings to be used. */
122    private Map<String, String> m_settings;
123
124    /** The upload folder. */
125    private String m_uploadFolder;
126
127    /** The site path to the resource to display. */
128    private String m_value;
129
130    /**
131     * Constructor.<p>
132     */
133    public CmsJspTagDisplay() {
134
135        m_parameterMap = new LinkedHashMap<>();
136        m_displayFormatterPaths = new HashMap<>();
137        m_displayFormatterIds = new HashMap<>();
138    }
139
140    /**
141     * Includes the formatter rendering the given element.<p>
142     *
143     * @param element the element
144     * @param formatter the formatter configuration bean
145     * @param cacheable true if the flex cache should be used for calling the display formatter
146     * @param editable if editable
147     * @param canCreate if new resources may be created
148     * @param canDelete if the resource may be deleted
149     * @param creationSiteMap the create location sub site
150     * @param postCreateHandler the post create handler
151     * @param uploadFolder the upload folder to use
152     * @param context the page context
153     * @param request the request
154     * @param response the response
155     */
156    public static void displayAction(
157        CmsContainerElementBean element,
158        I_CmsFormatterBean formatter,
159        boolean cacheable,
160        boolean editable,
161        boolean canCreate,
162        boolean canDelete,
163        String creationSiteMap,
164        String postCreateHandler,
165        String uploadFolder,
166        PageContext context,
167        ServletRequest request,
168        ServletResponse response) {
169
170        if (CmsFlexController.isCmsRequest(request)) {
171            // this will always be true if the page is called through OpenCms
172            CmsObject cms = CmsFlexController.getCmsObject(request);
173            CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfigurationWithCache(
174                cms,
175                cms.getRequestContext().getRootUri());
176            Locale locale = cms.getRequestContext().getLocale();
177            boolean isOnline = cms.getRequestContext().getCurrentProject().isOnlineProject();
178            CmsJspStandardContextBean contextBean = CmsJspStandardContextBean.getInstance(request);
179            CmsContainerElementBean parentElement = contextBean.getElement();
180
181            try {
182                if (formatter != null) {
183                    element.initResource(cms);
184                    element.initSettings(cms, adeConfig, formatter, locale, request, null);
185                    element.getSettings().put(DISPLAY_FORMATTER_SETTING, formatter.getKeyOrId());
186                    boolean openedEditable = false;
187                    contextBean.setElement(element);
188                    if (editable && contextBean.getIsEditMode()) {
189                        if (CmsJspTagEditable.getDirectEditProvider(context) == null) {
190                            I_CmsDirectEditProvider eb = new CmsAdvancedDirectEditProvider();
191                            eb.init(cms, CmsDirectEditMode.TRUE, element.getSitePath());
192                            request.setAttribute(I_CmsDirectEditProvider.ATTRIBUTE_DIRECT_EDIT_PROVIDER, eb);
193                        }
194
195                        openedEditable = CmsJspTagEdit.insertDirectEditStart(
196                            cms,
197                            context,
198                            element.getResource(),
199                            canCreate,
200                            canDelete,
201                            null,
202                            creationSiteMap,
203                            postCreateHandler,
204                            uploadFolder);
205                    }
206                    if (contextBean.getIsEditMode()) {
207                        CmsADESessionCache.getCache(
208                            (HttpServletRequest)(context.getRequest()),
209                            cms).setCacheContainerElement(element.editorHash(), element);
210                    }
211                    try {
212                        CmsJspTagInclude.includeTagAction(
213                            context,
214                            cms.getRequestContext().removeSiteRoot(formatter.getJspRootPath()),
215                            null,
216                            locale,
217                            false,
218                            isOnline && cacheable,
219                            CmsRequestUtil.createParameterMap(element.getSettings()),
220                            CmsRequestUtil.getAtrributeMap(request),
221                            request,
222                            response);
223                    } catch (Exception e) {
224                        LOG.error(e.getLocalizedMessage(), e);
225                    }
226                    if (openedEditable) {
227                        CmsJspTagEdit.insertDirectEditEnd(context);
228                    }
229                }
230            } catch (CmsException e) {
231                LOG.error(e.getLocalizedMessage(), e);
232            }
233            contextBean.setElement(parentElement);
234        }
235
236    }
237
238    /**
239     * Includes the formatter rendering the given element.<p>
240     *
241     * @param element the element
242     * @param formatter the formatter configuration bean
243     * @param context the page context
244     * @param request the request
245     * @param response the response
246     */
247    public static void displayAction(
248        CmsContainerElementBean element,
249        I_CmsFormatterBean formatter,
250        PageContext context,
251        ServletRequest request,
252        ServletResponse response) {
253
254        displayAction(element, formatter, true, false, false, false, null, null, null, context, request, response);
255    }
256
257    /**
258     * Includes the formatter rendering the given element.<p>
259     *
260     * @param elementResource the element resource
261     * @param formatter the formatter configuration bean
262     * @param settings the element settings
263     * @param cacheable true if the flex cache should be used for calling the display formatter
264     * @param editable if editable
265     * @param canCreate if new resources may be created
266     * @param canDelete if the resource may be deleted
267     * @param creationSiteMap the create location sub site
268     * @param postCreateHandler the post create handler
269     * @param uploadFolder the upload folder
270     * @param context the page context
271     * @param request the request
272     * @param response the response
273     */
274    public static void displayAction(
275        CmsResource elementResource,
276        I_CmsFormatterBean formatter,
277        Map<String, String> settings,
278        boolean cacheable,
279        boolean editable,
280        boolean canCreate,
281        boolean canDelete,
282        String creationSiteMap,
283        String postCreateHandler,
284        String uploadFolder,
285        PageContext context,
286        ServletRequest request,
287        ServletResponse response) {
288
289        CmsContainerElementBean element = new CmsContainerElementBean(
290            elementResource.getStructureId(),
291            formatter.getJspStructureId(),
292            settings,
293            false);
294        displayAction(
295            element,
296            formatter,
297            cacheable,
298            editable,
299            canCreate,
300            canDelete,
301            creationSiteMap,
302            postCreateHandler,
303            uploadFolder,
304            context,
305            request,
306            response);
307    }
308
309    /**
310     * If the setting key starts with the key or id of the given formatter, returns the remaining suffix, else null.
311     *
312     * @param config the current sitemap configuration
313     * @param formatter the formatter bean
314     * @param settingKey the setting key
315     *
316     * @return the remaining setting name suffix
317     */
318    public static String getSettingKeyForMatchingFormatterPrefix(
319        CmsADEConfigData config,
320        I_CmsFormatterBean formatter,
321        String settingKey) {
322
323        if (CmsElementUtil.isSystemSetting(settingKey)) {
324            return null;
325        }
326
327        int underscoreIndex = settingKey.indexOf("_");
328        if (underscoreIndex < 0) {
329            return null;
330        }
331        String prefix = settingKey.substring(0, underscoreIndex);
332        String suffix = settingKey.substring(underscoreIndex + 1);
333        I_CmsFormatterBean dynamicFmt = config.findFormatter(prefix, /*noWarn=*/true);
334        if (dynamicFmt == null) {
335            return null;
336        }
337        boolean keyMatch = (dynamicFmt.getKey() != null) && dynamicFmt.getKey().equals(formatter.getKey());
338        boolean idMatch = (dynamicFmt.getId() != null) && dynamicFmt.getId().equals(formatter.getId());
339        if (!keyMatch && !idMatch) {
340            return null;
341        }
342        if (!dynamicFmt.getSettings(config).containsKey(suffix)) {
343            return null;
344        }
345        return suffix;
346    }
347
348    /**
349     * Adds a display formatter.<p>
350     *
351     * @param type the resource type
352     * @param path the path to the formatter configuration file.<p>
353     */
354    public void addDisplayFormatter(String type, String path) {
355
356        m_displayFormatterPaths.put(type, path);
357    }
358
359    /**
360     * @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(java.lang.String, java.lang.String)
361     */
362    public void addParameter(String name, String value) {
363
364        // No null values allowed in parameters
365        if ((name == null) || (value == null)) {
366            return;
367        }
368
369        m_parameterMap.put(name, value);
370    }
371
372    /**
373     * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag()
374     */
375    @Override
376    public int doEndTag() throws JspException {
377
378        ServletRequest request = pageContext.getRequest();
379        ServletResponse response = pageContext.getResponse();
380        if (CmsFlexController.isCmsRequest(request)) {
381            // this will always be true if the page is called through OpenCms
382            CmsObject cms = CmsFlexController.getCmsObject(request);
383            try {
384                boolean isOnline = cms.getRequestContext().getCurrentProject().isOnlineProject();
385                CmsResource res = null;
386                if (CmsUUID.isValidUUID(m_value)) {
387                    CmsUUID structureId = new CmsUUID(m_value);
388                    res = isOnline
389                    ? cms.readResource(structureId)
390                    : cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
391                } else {
392                    res = isOnline
393                    ? cms.readResource(m_value)
394                    : cms.readResource(m_value, CmsResourceFilter.IGNORE_EXPIRATION);
395                }
396
397                CmsObject cmsForFormatterLookup = cms;
398                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_baseUri)) {
399                    cmsForFormatterLookup = OpenCms.initCmsObject(cms);
400                    cmsForFormatterLookup.getRequestContext().setUri(m_baseUri);
401                }
402                I_CmsFormatterBean formatter = getFormatterForType(cmsForFormatterLookup, res, isOnline);
403                CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache(
404                    cmsForFormatterLookup,
405                    cms.getRequestContext().getRootUri());
406                if (formatter == null) {
407                    String error = "cms:display - could not find display formatter for " + m_value + "\n";
408                    try {
409                        error += "\n\nTag instance: " + ReflectionToStringBuilder.toString(this);
410                    } catch (Exception e) {
411                        // ignore
412                    }
413                    throw new JspException(error);
414                }
415
416                Map<String, String> settings = prepareSettings(config, formatter);
417
418                displayAction(
419                    res,
420                    formatter,
421                    settings,
422                    isCacheable(),
423                    m_editable,
424                    m_canCreate,
425                    m_canDelete,
426                    m_creationSiteMap,
427                    m_postCreateHandler,
428                    m_uploadFolder,
429                    pageContext,
430                    request,
431                    response);
432            } catch (CmsException e) {
433                LOG.error(e.getLocalizedMessage(), e);
434            }
435        }
436        release();
437        return EVAL_PAGE;
438    }
439
440    /**
441     * @see javax.servlet.jsp.tagext.BodyTagSupport#doStartTag()
442     */
443    @Override
444    public int doStartTag() {
445
446        if (Boolean.valueOf(m_passSettings).booleanValue()) {
447            CmsContainerElementBean element = CmsJspStandardContextBean.getInstance(
448                pageContext.getRequest()).getElement();
449            if (element != null) {
450                m_parameterMap.putAll(element.getSettings());
451            }
452        }
453        if (m_settings != null) {
454            m_parameterMap.putAll(m_settings);
455        }
456
457        return EVAL_BODY_BUFFERED;
458    }
459
460    /**
461     * Returns the editable.<p>
462     *
463     * @return the editable
464     */
465    public boolean getEditable() {
466
467        return m_editable;
468    }
469
470    /**
471     * Returns the passSettings.<p>
472     *
473     * @return the passSettings
474     */
475    public boolean getPassSettings() {
476
477        return m_passSettings;
478    }
479
480    /**
481     * Returns the element settings to be used.<p>
482     *
483     * @return the element settings to be used
484     */
485    public Map<String, String> getSettings() {
486
487        return m_settings;
488    }
489
490    /**
491     * Returns the value.<p>
492     *
493     * @return the value
494     */
495    public String getValue() {
496
497        return m_value;
498    }
499
500    /**
501     * @see javax.servlet.jsp.tagext.BodyTagSupport#release()
502     */
503    @Override
504    public void release() {
505
506        super.release();
507        m_parameterMap.clear();
508        m_displayFormatterPaths.clear();
509        m_displayFormatterIds.clear();
510        m_settings = null;
511        m_passSettings = false;
512        m_editable = false;
513        m_value = null;
514    }
515
516    /**
517     * Sets the base URI to use for finding the 'default' display formatter.
518     *
519     * @param uri the base URI
520     */
521    public void setBaseUri(String uri) {
522
523        m_baseUri = uri;
524    }
525
526    /**
527     * Enables/disables the use of the flex cache for the display formatter include.
528     *
529     * @param cacheable true if the flex cache should be used for the display formatter include
530     */
531    public void setCacheable(boolean cacheable) {
532
533        m_cacheable = Boolean.valueOf(cacheable);
534    }
535
536    /** Setter for the "create" attribute of the tag.
537     * @param canCreate value of the tag's attribute "create".
538     */
539    public void setCreate(boolean canCreate) {
540
541        m_canCreate = canCreate;
542    }
543
544    /** Setter for the "create" attribute of the tag.
545     * @param canCreate value of the tag's attribute "create".
546     */
547    public void setCreate(String canCreate) {
548
549        m_canCreate = Boolean.valueOf(canCreate).booleanValue();
550    }
551
552    /** Setter for the "creationSiteMap" attribute of the tag.
553     * @param sitePath value of the "creationSiteMap" attribute of the tag.
554     */
555    public void setCreationSiteMap(String sitePath) {
556
557        m_creationSiteMap = sitePath;
558    }
559
560    /**Setter for the "delete" attribute of the tag.
561     * @param canDelete value of the "delete" attribute of the tag.
562     */
563    public void setDelete(boolean canDelete) {
564
565        m_canDelete = canDelete;
566    }
567
568    /**Setter for the "delete" attribute of the tag.
569     * @param canDelete value of the "delete" attribute of the tag.
570     */
571    public void setDelete(String canDelete) {
572
573        m_canDelete = Boolean.valueOf(canDelete).booleanValue();
574    }
575
576    /**
577     * Sets the items.<p>
578     *
579     * @param displayFormatters the items to set
580     */
581    public void setDisplayFormatters(Object displayFormatters) {
582
583        if (displayFormatters instanceof List) {
584            for (Object formatterItem : ((List<?>)displayFormatters)) {
585                if (formatterItem instanceof CmsJspContentAccessValueWrapper) {
586                    addFormatter((CmsJspContentAccessValueWrapper)formatterItem);
587                }
588            }
589        } else if (displayFormatters instanceof CmsJspContentAccessValueWrapper) {
590            addFormatter((CmsJspContentAccessValueWrapper)displayFormatters);
591        } else if (displayFormatters instanceof String) {
592            String[] temp = ((String)displayFormatters).split(CmsXmlDisplayFormatterValue.SEPARATOR);
593            if (temp.length == 2) {
594                addDisplayFormatter(temp[0], temp[1]);
595            }
596        }
597    }
598
599    /**
600     * Sets the editable.<p>
601     *
602     * @param editable the editable to set
603     */
604    public void setEditable(boolean editable) {
605
606        m_editable = editable;
607    }
608
609    /**
610     * Sets the editable.<p>
611     *
612     * @param editable the editable to set
613     */
614    public void setEditable(String editable) {
615
616        m_editable = Boolean.valueOf(editable).booleanValue();
617    }
618
619    /**
620     * Sets the passSettings.<p>
621     *
622     * @param passSettings the passSettings to set
623     */
624    public void setPassSettings(boolean passSettings) {
625
626        m_passSettings = passSettings;
627    }
628
629    /**
630     * Sets the passSettings.<p>
631     *
632     * @param passSettings the passSettings to set
633     */
634    public void setPassSettings(String passSettings) {
635
636        m_passSettings = Boolean.valueOf(passSettings).booleanValue();
637    }
638
639    /** Setter for the "postCreateHandler" attribute of the tag.
640     * @param postCreateHandler fully qualified class name of the {@link I_CmsCollectorPostCreateHandler} to use.
641     */
642    public void setPostCreateHandler(final String postCreateHandler) {
643
644        m_postCreateHandler = postCreateHandler;
645    }
646
647    /**
648     * Sets the element settings to be used.<p>
649     *
650     * @param settings the element settings to be used
651     */
652    public void setSettings(Map<String, String> settings) {
653
654        m_settings = settings;
655    }
656
657    /**
658     * Sets the upload folder.
659     *
660     * @param uploadFolder the upload folder
661     */
662    public void setUploadFolder(String uploadFolder) {
663
664        m_uploadFolder = uploadFolder;
665    }
666
667    /**
668     * Sets the value.<p>
669     *
670     * @param value the value to set
671     */
672    public void setValue(String value) {
673
674        m_value = value;
675    }
676
677    /**
678     * Adds a formatter.<p>
679     *
680     * @param formatterItem the formatter value
681     */
682    private void addFormatter(CmsJspContentAccessValueWrapper formatterItem) {
683
684        I_CmsXmlContentValue val = formatterItem.getContentValue();
685        if (val instanceof CmsXmlDisplayFormatterValue) {
686            CmsXmlDisplayFormatterValue value = (CmsXmlDisplayFormatterValue)val;
687            String type = value.getDisplayType();
688            String formatterId = value.getFormatterId();
689            if (formatterId != null) {
690                m_displayFormatterIds.put(type, formatterId);
691            }
692        }
693    }
694
695    /**
696     * Returns the config for the requested resource, or <code>null</code> if not available.<p>
697     *
698     * @param cms the cms context
699     * @param resource the resource
700     * @param isOnline the is online flag
701     *
702     * @return the formatter configuration bean
703     */
704    private I_CmsFormatterBean getFormatterForType(CmsObject cms, CmsResource resource, boolean isOnline) {
705
706        String typeName = OpenCms.getResourceManager().getResourceType(resource).getTypeName();
707        CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache(
708            cms,
709            cms.getRequestContext().getRootUri());
710        I_CmsFormatterBean result = null;
711        if (m_displayFormatterPaths.containsKey(typeName)) {
712            try {
713                CmsResource res = cms.readResource(m_displayFormatterPaths.get(typeName));
714                result = OpenCms.getADEManager().getCachedFormatters(isOnline).getFormatters().get(
715                    res.getStructureId());
716            } catch (CmsException e) {
717                LOG.error(e.getLocalizedMessage(), e);
718            }
719        } else if (m_displayFormatterIds.containsKey(typeName)) {
720            result = config.findFormatter(m_displayFormatterIds.get(typeName));
721        } else {
722            if (config != null) {
723                CmsFormatterConfiguration formatters = config.getFormatters(cms, resource);
724                if (formatters != null) {
725                    result = formatters.getDisplayFormatter();
726                }
727            }
728        }
729        return result;
730    }
731
732    /**
733     * Checks if this tag instance should use the flex cache for including the formatter.
734     *
735     * @return true if this tag instance should use the flex cache for including the formatter
736     */
737    private boolean isCacheable() {
738
739        return (m_cacheable == null) || m_cacheable.booleanValue();
740    }
741
742    /**
743     * Prepares the settings before the call to displayAction().
744     *
745     * @param config the sitemap configuration
746     * @param formatter the display formatter
747     *
748     * @return the settings to use
749     */
750    private Map<String, String> prepareSettings(CmsADEConfigData config, I_CmsFormatterBean formatter) {
751
752        Map<String, String> settings = new HashMap<String, String>();
753        for (Entry<String, String> entry : m_parameterMap.entrySet()) {
754            if (CmsContainerElement.ELEMENT_INSTANCE_ID.equals(entry.getKey())) {
755                // remove any instance id to make sure to generate a unique one
756                continue;
757            }
758            String fmtSetting = getSettingKeyForMatchingFormatterPrefix(config, formatter, entry.getKey());
759            if (entry.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) {
760                settings.put(entry.getKey(), formatter.getId());
761            } else if (fmtSetting != null) {
762                settings.put(fmtSetting, entry.getValue());
763            } else if (!settings.containsKey(entry.getKey())) {
764                settings.put(entry.getKey(), entry.getValue());
765            }
766        }
767        return settings;
768    }
769}