001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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 GmbH & Co. KG, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.CmsContainerpageService;
032import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
033import org.opencms.ade.containerpage.CmsElementUtil;
034import org.opencms.ade.containerpage.shared.CmsContainer;
035import org.opencms.ade.containerpage.shared.CmsContainerElement;
036import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
037import org.opencms.file.CmsGroup;
038import org.opencms.file.CmsObject;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsUser;
041import org.opencms.file.CmsVfsResourceNotFoundException;
042import org.opencms.flex.CmsFlexController;
043import org.opencms.gwt.shared.CmsGwtConstants;
044import org.opencms.gwt.shared.CmsTemplateContextInfo;
045import org.opencms.i18n.CmsEncoder;
046import org.opencms.jsp.CmsJspTagAddParams.ParamState;
047import org.opencms.jsp.util.CmsJspStandardContextBean;
048import org.opencms.jsp.util.CmsJspStandardContextBean.CmsContainerElementWrapper;
049import org.opencms.loader.CmsJspLoader;
050import org.opencms.loader.CmsLoaderException;
051import org.opencms.loader.CmsTemplateContext;
052import org.opencms.loader.CmsTemplateContextManager;
053import org.opencms.loader.I_CmsTemplateContextProvider;
054import org.opencms.main.CmsException;
055import org.opencms.main.CmsIllegalStateException;
056import org.opencms.main.CmsLog;
057import org.opencms.main.OpenCms;
058import org.opencms.security.CmsPermissionViolationException;
059import org.opencms.security.CmsRole;
060import org.opencms.util.CmsRequestUtil;
061import org.opencms.util.CmsStringUtil;
062import org.opencms.util.CmsUUID;
063import org.opencms.xml.containerpage.CmsADESessionCache;
064import org.opencms.xml.containerpage.CmsContainerBean;
065import org.opencms.xml.containerpage.CmsContainerElementBean;
066import org.opencms.xml.containerpage.CmsContainerPageBean;
067import org.opencms.xml.containerpage.CmsFormatterConfiguration;
068import org.opencms.xml.containerpage.CmsGroupContainerBean;
069import org.opencms.xml.containerpage.CmsXmlContainerPage;
070import org.opencms.xml.containerpage.CmsXmlContainerPageFactory;
071import org.opencms.xml.containerpage.CmsXmlGroupContainer;
072import org.opencms.xml.containerpage.CmsXmlGroupContainerFactory;
073import org.opencms.xml.containerpage.CmsXmlInheritGroupContainerHandler;
074import org.opencms.xml.containerpage.I_CmsFormatterBean;
075import org.opencms.xml.templatemapper.CmsTemplateMapper;
076
077import java.io.IOException;
078import java.util.ArrayList;
079import java.util.Collections;
080import java.util.HashMap;
081import java.util.HashSet;
082import java.util.List;
083import java.util.Locale;
084import java.util.Map;
085import java.util.Map.Entry;
086import java.util.Set;
087
088import javax.servlet.ServletRequest;
089import javax.servlet.ServletResponse;
090import javax.servlet.http.HttpServletRequest;
091import javax.servlet.http.HttpServletResponse;
092import javax.servlet.jsp.JspException;
093import javax.servlet.jsp.tagext.BodyContent;
094import javax.servlet.jsp.tagext.BodyTagSupport;
095import javax.servlet.jsp.tagext.TryCatchFinally;
096
097import org.apache.commons.lang3.ClassUtils;
098import org.apache.commons.logging.Log;
099
100/**
101 * Provides access to the page container elements.<p>
102 *
103 * @since 8.0
104 */
105public class CmsJspTagContainer extends BodyTagSupport implements TryCatchFinally, I_CmsJspTagParamParent {
106
107    /** Default number of max elements in the container in case no value has been set. */
108    public static final String DEFAULT_MAX_ELEMENTS = "100";
109
110    /** The detail function container name. */
111    public static final String DETAIL_FUNCTION_CONTAINER_NAME = "FunctionDefault";
112
113    /** HTML used for invisible dummy elements. */
114    public static final String DUMMY_ELEMENT = String.format(
115        "<%s class='%s' style='display: none !important;'></%s>",
116        CmsGwtConstants.TAG_OC_HIDDEN_ELEMENT,
117        CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER,
118        CmsGwtConstants.TAG_OC_HIDDEN_ELEMENT);
119
120    /** Closing tag for hidden elements. */
121    public static final String DUMMY_ELEMENT_END = "</" + CmsGwtConstants.TAG_OC_HIDDEN_ELEMENT + ">";
122
123    /** Opening tag for hidden elements. */
124    public static final String DUMMY_ELEMENT_START = String.format(
125        "<%s class='%s' style='display: none !important;'>",
126        CmsGwtConstants.TAG_OC_HIDDEN_ELEMENT,
127        CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER);
128
129    /** The default tag name constant. */
130    private static final String DEFAULT_TAG_NAME = "div";
131
132    /** The log object for this class. */
133    private static final Log LOG = CmsLog.getLog(CmsJspTagContainer.class);
134
135    /** Serial version UID required for safe serialization. */
136    private static final long serialVersionUID = -1228397990961282556L;
137
138    /** The evaluated body content if available. */
139    private String m_bodyContent;
140
141    /** If false, formatters are always included in non-cacheable mode, otherwise they are included in cacheable mode in the Online project only. */
142    private boolean m_cacheable = true;
143
144    /** States if this container should only be displayed on detail pages. */
145    private boolean m_detailOnly;
146
147    /** The detail-view attribute value. */
148    private boolean m_detailView;
149
150    /** The editable by tag attribute. A comma separated list of OpenCms principals. */
151    private String m_editableBy;
152
153    /** Indicating that the container page editor is active for the current request. */
154    private boolean m_editableRequest;
155
156    /** Indicates this container is nested within a model group, only set for editable requests. */
157    private boolean m_hasModelGroupAncestor;
158
159    /** The maxElements attribute value. */
160    private String m_maxElements;
161
162    /** The name attribute value. */
163    private String m_name;
164
165    /**
166     * The container name prefix to use for nested container names.
167     * If empty the element instance id of the parent element will be used.
168     **/
169    private String m_namePrefix;
170
171    /** The optional container parameter. */
172    private String m_param;
173
174    /** The parameter state. */
175    private CmsJspTagAddParams.ParamState m_paramState;
176
177    /** The parent container. */
178    private CmsContainerBean m_parentContainer;
179
180    /** The parent element to this container. */
181    private CmsContainerElementBean m_parentElement;
182
183    /** The container setting presets. */
184    private HashMap<String, String> m_settingPresets;
185
186    /** The tag attribute value. */
187    private String m_tag;
188
189    /** The class attribute value. */
190    private String m_tagClass;
191
192    /** The type attribute value. */
193    private String m_type;
194
195    /** The container width as a string. */
196    private String m_width;
197
198    /**
199     * Ensures the appropriate formatter configuration ID is set in the element settings.<p>
200     *
201     * @param cms the cms context
202     * @param element the element bean
203     * @param adeConfig the ADE configuration data
204     * @param containerName the container name
205     * @param containerType the container type
206     * @param containerWidth the container width
207     *
208     * @return the formatter configuration bean, may be <code>null</code> if no formatter available or a schema formatter is used
209     */
210    public static I_CmsFormatterBean ensureValidFormatterSettings(
211        CmsObject cms,
212        CmsContainerElementBean element,
213        CmsADEConfigData adeConfig,
214        String containerName,
215        String containerType,
216        int containerWidth) {
217
218        I_CmsFormatterBean formatterBean = getFormatterConfigurationForElement(
219            cms,
220            element,
221            adeConfig,
222            containerName,
223            containerType,
224            containerWidth);
225        String settingsKey = CmsFormatterConfig.getSettingsKeyForContainer(containerName);
226        if (formatterBean != null) {
227            String keyOrId = formatterBean.getKeyOrId();
228            if (keyOrId == null) {
229                keyOrId = CmsFormatterConfig.SCHEMA_FORMATTER_ID + formatterBean.getJspStructureId().toString();
230            }
231            element.getSettings().put(settingsKey, keyOrId);
232            element.setFormatterId(formatterBean.getJspStructureId());
233        }
234        return formatterBean;
235    }
236
237    /**
238     * Returns the formatter configuration for the given element.<p>
239     *
240     * @param cms the cms context
241     * @param element the element bean
242     * @param adeConfig the ADE configuration
243     * @param containerName the container name
244     * @param containerType the container type
245     * @param containerWidth the container width
246     *
247     * @return the formatter configuration
248     */
249    public static I_CmsFormatterBean getFormatterConfigurationForElement(
250        CmsObject cms,
251        CmsContainerElementBean element,
252        CmsADEConfigData adeConfig,
253        String containerName,
254        String containerType,
255        int containerWidth) {
256
257        I_CmsFormatterBean formatterBean = null;
258        String settingsKey = CmsFormatterConfig.getSettingsKeyForContainer(containerName);
259        String formatterSetting = element.getSettings().get(settingsKey);
260        CmsFormatterConfiguration formatterConfig = null;
261        if (formatterSetting != null) {
262            formatterConfig = adeConfig.getFormatters(cms, element.getResource());
263            // getFormattersForKey also works for the schema_formaterXXXXXX setting values
264            List<I_CmsFormatterBean> candidates = formatterConfig.getFormattersForKey(formatterSetting);
265            if (candidates.size() > 0) {
266                formatterBean = candidates.get(0);
267            } else {
268                formatterBean = adeConfig.findFormatter(formatterSetting);
269            }
270        }
271
272        if ((formatterBean == null) && (element.getFormatterId() != null) && !element.getFormatterId().isNullUUID()) {
273            if (formatterConfig == null) {
274                formatterConfig = adeConfig.getFormatters(cms, element.getResource());
275            }
276
277            for (I_CmsFormatterBean formatter : adeConfig.getFormatters(
278                cms,
279                element.getResource()).getAllMatchingFormatters(containerType, containerWidth)) {
280
281                if (element.getFormatterId().equals(formatter.getJspStructureId())) {
282                    String formatterConfigId = formatter.getId();
283                    if (formatterConfigId == null) {
284                        formatterConfigId = CmsFormatterConfig.SCHEMA_FORMATTER_ID
285                            + element.getFormatterId().toString();
286                    }
287                    formatterBean = formatter;
288                    break;
289                }
290            }
291        }
292
293        if (formatterBean == null) {
294
295            formatterBean = adeConfig.getFormatters(cms, element.getResource()).getDefaultFormatter(
296                containerType,
297                containerWidth);
298        }
299        return formatterBean;
300    }
301
302    /**
303     * Returns the element group elements.<p>
304     *
305     * @param cms the current cms context
306     * @param element group element
307     * @param req the servlet request
308     * @param containerType the container type
309     *
310     * @return the elements of this group
311     *
312     * @throws CmsException if something goes wrong
313     */
314    public static List<CmsContainerElementBean> getGroupContainerElements(
315        CmsObject cms,
316        CmsContainerElementBean element,
317        ServletRequest req,
318        String containerType)
319    throws CmsException {
320
321        List<CmsContainerElementBean> subElements;
322        CmsXmlGroupContainer xmlGroupContainer = CmsXmlGroupContainerFactory.unmarshal(cms, element.getResource(), req);
323        CmsGroupContainerBean groupContainer = xmlGroupContainer.getGroupContainer(cms);
324        groupContainer = CmsTemplateMapper.get(req).transformGroupContainer(
325            cms,
326            groupContainer,
327            xmlGroupContainer.getFile().getRootPath());
328        if (!CmsElementUtil.checkGroupAllowed(containerType, groupContainer)) {
329            LOG.warn(
330                new CmsIllegalStateException(
331                    Messages.get().container(
332                        Messages.ERR_XSD_NO_TEMPLATE_FORMATTER_3,
333                        element.getResource().getRootPath(),
334                        OpenCms.getResourceManager().getResourceType(element.getResource()).getTypeName(),
335                        containerType)));
336            return Collections.emptyList();
337        }
338        subElements = groupContainer.getElements();
339        return subElements;
340    }
341
342    /**
343     * Reads elements from an inherited container.<p>
344     *
345     * @param cms the current CMS context
346     * @param element the element which references the inherited container
347     *
348     * @return the container elements
349     */
350
351    public static List<CmsContainerElementBean> getInheritedContainerElements(
352        CmsObject cms,
353        CmsContainerElementBean element) {
354
355        CmsResource resource = element.getResource();
356        return CmsXmlInheritGroupContainerHandler.loadInheritContainerElements(cms, resource);
357    }
358
359    /**
360     * Returns the prefixed nested container name.<p>
361     * This will be either {parentInstanceId}-{name} or {namePrefix}-{name} or in case namePrefix equals 'none' {name} only.<p>
362     *
363     * @param name the container name
364     * @param parentIstanceId the parent instance id
365     * @param namePrefix the name prefix attribute
366     *
367     * @return the nested container name
368     */
369    public static String getNestedContainerName(String name, String parentIstanceId, String namePrefix) {
370
371        String prefix;
372        if (CmsStringUtil.isEmptyOrWhitespaceOnly(namePrefix)) {
373            prefix = parentIstanceId + "-";
374        } else if ("none".equals(namePrefix)) {
375            prefix = "";
376        } else {
377            prefix = namePrefix + "-";
378        }
379        return prefix + name;
380    }
381
382    /**
383     * Creates the closing tag for the container.<p>
384     *
385     * @param tagName the tag name
386     *
387     * @return the closing tag
388     */
389    protected static String getTagClose(String tagName) {
390
391        return "</" + tagName + ">";
392    }
393
394    /**
395     * Creates the opening tag for the container assigning the appropriate id and class attributes.<p>
396     *
397     * @param tagName the tag name
398     * @param containerName the container name used as id attribute value
399     * @param tagClass the tag class attribute value
400     * @param nested true if this is a nested container
401     * @param online true if we are in the online project
402     * @param containerData the container data
403     *
404     * @return the opening tag
405     */
406    protected static String getTagOpen(
407        String tagName,
408        String containerName,
409        String tagClass,
410        boolean nested,
411        boolean online,
412        String containerData) {
413
414        StringBuffer buffer = new StringBuffer(32);
415        buffer.append("<").append(tagName).append(" ");
416        if (online && nested) {
417            // omit generated ids when online
418        } else {
419            buffer.append(" id=\"").append(containerName).append("\" ");
420        }
421        if (containerData != null) {
422            buffer.append(" " + CmsGwtConstants.ATTR_DATA_CONTAINER + "=\"").append(containerData).append("\" ");
423            // set the marker CSS class
424            tagClass = tagClass == null
425            ? CmsContainerElement.CLASS_CONTAINER
426            : tagClass + " " + CmsContainerElement.CLASS_CONTAINER;
427        }
428        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(tagClass)) {
429            buffer.append("class=\"").append(tagClass).append("\" ");
430        }
431        buffer.append(">");
432        return buffer.toString();
433    }
434
435    /**
436     * @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(java.lang.String, java.lang.String)
437     */
438    public void addParameter(String name, String value) {
439
440        if (m_paramState != null) {
441            m_paramState.addParameter(name, value);
442        }
443    }
444
445    /**
446     * @see javax.servlet.jsp.tagext.BodyTagSupport#doAfterBody()
447     */
448    @SuppressWarnings("resource")
449    @Override
450    public int doAfterBody() {
451
452        // store the evaluated body content for later use
453        BodyContent bc = getBodyContent();
454        if (bc != null) {
455            m_bodyContent = bc.getString();
456            try {
457                bc.clear();
458            } catch (IOException e) {
459                LOG.error(e.getLocalizedMessage(), e);
460            }
461        }
462        return SKIP_BODY;
463    }
464
465    /**
466     * @see javax.servlet.jsp.tagext.TryCatchFinally#doCatch(java.lang.Throwable)
467     */
468    public void doCatch(Throwable t) throws Throwable {
469
470        throw t;
471    }
472
473    /**
474     * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
475     */
476    @Override
477    public int doEndTag() throws JspException {
478
479        ServletRequest req = pageContext.getRequest();
480        // This will always be true if the page is called through OpenCms
481        if (CmsFlexController.isCmsRequest(req)) {
482
483            try {
484                CmsFlexController controller = CmsFlexController.getController(req);
485                CmsObject cms = controller.getCmsObject();
486                String requestUri = cms.getRequestContext().getUri();
487                Locale locale = cms.getRequestContext().getLocale();
488                CmsJspStandardContextBean standardContext = CmsJspStandardContextBean.getInstance(req);
489                standardContext.initPage();
490                m_editableRequest = standardContext.getIsEditMode();
491                m_parentElement = standardContext.getRawElement();
492                m_parentContainer = standardContext.getContainer();
493                m_hasModelGroupAncestor = m_editableRequest ? hasModelGroupAncestor(standardContext) : false;
494                CmsContainerPageBean containerPage = standardContext.getPage();
495                CmsResource detailContent = standardContext.getDetailContent();
496                CmsResource detailFunctionPage = standardContext.getDetailFunctionPage();
497                // get the container
498                CmsContainerBean container = null;
499                boolean detailOnly = m_detailOnly || ((m_parentContainer != null) && m_parentContainer.isDetailOnly());
500                if (detailOnly) {
501                    if (detailContent == null) {
502                        // this is no detail page, so the detail only container will not be rendered at all
503                        resetState();
504                        return EVAL_PAGE;
505                    } else {
506                        String pageRootPath = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri());
507                        CmsContainerPageBean detailOnlyPage = CmsDetailOnlyContainerUtil.getDetailOnlyPage(
508                            cms,
509                            req,
510                            pageRootPath);
511                        if (detailOnlyPage != null) {
512                            container = detailOnlyPage.getContainers().get(getName());
513                        }
514                        if ((container == null) && m_editableRequest && (containerPage != null)) {
515                            // this is for the case where the current container is the nested container of a model group which the user is dragging into a detail container
516                            container = containerPage.getContainers().get(getName());
517                        }
518                    }
519                } else if (containerPage != null) {
520                    container = containerPage.getContainers().get(getName());
521                }
522                // get the maximal number of elements
523                int maxElements = getMaxElements(requestUri);
524                if (container == null) {
525                    container = new CmsContainerBean(
526                        getName(),
527                        getType(),
528                        m_parentElement != null ? m_parentElement.getInstanceId() : null,
529                        (m_parentContainer == null) || (m_detailOnly && !m_parentContainer.isDetailOnly()),
530                        maxElements,
531                        Collections.<CmsContainerElementBean> emptyList());
532                } else if ((m_parentElement != null)
533                    && !m_detailOnly //ignore parent information for detail only containers to render content on different detail pages.
534                    && !m_parentElement.getInstanceId().equals(container.getParentInstanceId())) {
535                    // the container parent instance id does not match the parent element instance id, skip rendering to avoid recursion
536                    LOG.error(
537                        new CmsIllegalStateException(
538                            Messages.get().container(
539                                Messages.ERR_INVALID_CONTAINER_PARENT_2,
540                                getName(),
541                                m_parentElement.getInstanceId())));
542                    resetState();
543                    return EVAL_PAGE;
544                }
545                // set the parameter
546                container.setParam(getParam());
547                // set the detail only flag
548                container.setDetailOnly(detailOnly);
549                boolean isUsedAsDetailView = false;
550                if (m_detailView && ((detailContent != null) || (detailFunctionPage != null))) {
551                    isUsedAsDetailView = true;
552                }
553                // create tag for container
554                String tagName = CmsStringUtil.isEmptyOrWhitespaceOnly(getTag()) ? DEFAULT_TAG_NAME : getTag();
555                pageContext.getOut().print(
556                    getTagOpen(
557                        tagName,
558                        getName(),
559                        getTagClass(),
560                        isNested(),
561                        !m_editableRequest,
562                        m_editableRequest ? getContainerData(cms, maxElements, isUsedAsDetailView, detailOnly) : null));
563
564                standardContext.setContainer(container);
565                // validate the type
566                if (!getType().equals(container.getType())) {
567                    container.setType(getType());
568                    LOG.warn(
569                        new CmsIllegalStateException(
570                            Messages.get().container(
571                                Messages.LOG_WRONG_CONTAINER_TYPE_4,
572                                new Object[] {requestUri, locale, getName(), getType()})));
573                }
574
575                // update the cache
576                container.setMaxElements(maxElements);
577                container.setWidth("" + getContainerWidth());
578                List<CmsContainerElementBean> allElements = new ArrayList<CmsContainerElementBean>();
579                CmsContainerElementBean detailElement = null;
580                if (isUsedAsDetailView) {
581                    if (detailContent != null) {
582                        detailElement = generateDetailViewElement(req, cms, detailContent, container);
583                    } else {
584                        detailElement = getDetailFunctionElement(cms, detailFunctionPage, req);
585                    }
586                }
587                if (detailElement != null) {
588                    allElements.add(detailElement);
589                } else {
590                    allElements.addAll(container.getElements());
591                }
592                // iterate over elements to render
593                int numRenderedElements = 0;
594                Set<String> skippedTypes = new HashSet<>();
595                for (CmsContainerElementBean elementBean : allElements) {
596                    // if we are not in edit mode, and displaying a detail container without having a detail content,
597                    // skip the first element of any type for which this page is a detail page.
598                    // (those are the elements used as 'setting templates' for their respective types)
599                    boolean skipDetailTemplateElement = false;
600                    String type = elementBean.getTypeName();
601                    try {
602                        skipDetailTemplateElement = !skippedTypes.contains(type)
603                            && !m_editableRequest
604                            && m_detailView
605                            && (detailElement == null)
606                            && OpenCms.getADEManager().isDetailPage(cms, standardContext.getPageResource())
607                            && OpenCms.getADEManager().getDetailPages(cms, type).contains(
608                                CmsResource.getFolderPath(standardContext.getPageResource().getRootPath()));
609                    } catch (Exception e) {
610                        LOG.error(e.getLocalizedMessage(), e);
611                    }
612                    if (skipDetailTemplateElement) {
613                        skippedTypes.add(type);
614                    } else {
615                        try {
616                            boolean rendered = renderContainerElement(
617                                (HttpServletRequest)req,
618                                cms,
619                                standardContext,
620                                elementBean,
621                                locale,
622                                numRenderedElements >= maxElements);
623                            if (rendered) {
624                                numRenderedElements += 1;
625                            }
626                        } catch (Exception e) {
627                            if (LOG.isErrorEnabled()) {
628                                LOG.error(e.getLocalizedMessage(), e);
629                            }
630                        }
631                    }
632                }
633                if ((numRenderedElements == 0) && (m_bodyContent != null) && CmsJspTagEditable.isEditableRequest(req)) {
634                    // the container is empty, print the evaluated body content
635                    pageContext.getOut().print(m_bodyContent);
636                }
637                // close tag for container
638                pageContext.getOut().print(getTagClose(tagName));
639            } catch (Exception ex) {
640                if (LOG.isErrorEnabled()) {
641                    LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, "container"), ex);
642                }
643                throw new javax.servlet.jsp.JspException(ex);
644            }
645        }
646
647        resetState();
648        return super.doEndTag();
649    }
650
651    /**
652     * @see javax.servlet.jsp.tagext.TryCatchFinally#doFinally()
653     */
654    public void doFinally() {
655
656        if (m_paramState != null) {
657            m_paramState.undoChanges();
658            m_paramState = null;
659        }
660    }
661
662    /**
663     * Internal action method.<p>
664     *
665     * @return EVAL_BODY_BUFFERED
666     *
667     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
668     */
669    @Override
670    public int doStartTag() {
671
672        if (CmsFlexController.isCmsRequest(pageContext.getRequest())) {
673            m_paramState = new ParamState(
674                CmsFlexController.getController(pageContext.getRequest()).getCurrentRequest());
675            m_paramState.init();
676        }
677        return EVAL_BODY_BUFFERED;
678    }
679
680    /**
681     * Returns the boolean value if this container is target of detail views.<p>
682     *
683     * @return <code>true</code> or <code>false</code>
684     */
685    public String getDetailview() {
686
687        return String.valueOf(m_detailView);
688    }
689
690    /**
691     * Returns the editable by tag attribute.<p>
692     *
693     * @return the editable by tag attribute
694     */
695    public String getEditableby() {
696
697        return m_editableBy;
698    }
699
700    /**
701     * Returns the maxElements attribute value.<p>
702     *
703     * @return the maxElements attribute value
704     */
705    public String getMaxElements() {
706
707        return CmsStringUtil.isEmptyOrWhitespaceOnly(m_maxElements) ? DEFAULT_MAX_ELEMENTS : m_maxElements;
708    }
709
710    /**
711     * Returns the container name, in case of nested containers with a prefix to guaranty uniqueness.<p>
712     *
713     * @return String the container name
714     */
715    public String getName() {
716
717        if (isNested()) {
718            return getNestedContainerName(m_name, m_parentElement.getInstanceId(), m_namePrefix);
719        }
720        return m_name;
721    }
722
723    /**
724     * Returns the name prefix.<p>
725     *
726     * @return the namePrefix
727     */
728    public String getNameprefix() {
729
730        return m_namePrefix;
731    }
732
733    /**
734     * Returns the (optional) container parameter.<p>
735     *
736     * This is useful for a dynamically generated nested container,
737     * to pass information to the formatter used inside that container.
738     *
739     * If no parameters have been set, this will return <code>null</code>
740     *
741     * @return the (optional) container parameter
742     */
743    public String getParam() {
744
745        return m_param;
746    }
747
748    /**
749     * Returns the tag attribute.<p>
750     *
751     * @return the tag attribute
752     */
753    public String getTag() {
754
755        return m_tag;
756    }
757
758    /**
759     * Returns the tag class attribute.<p>
760     *
761     * @return the tag class attribute
762     */
763    public String getTagClass() {
764
765        return m_tagClass;
766    }
767
768    /**
769     * Returns the type attribute value.<p>
770     *
771     * If the container type has not been set, the name is substituted as type.<p>
772     *
773     * @return the type attribute value
774     */
775    public String getType() {
776
777        return CmsStringUtil.isEmptyOrWhitespaceOnly(m_type) ? getName() : m_type;
778    }
779
780    /**
781     * Returns the container width as a string.<p>
782     *
783     * @return the container width as a string
784     */
785    public String getWidth() {
786
787        return m_width;
788    }
789
790    /**
791     * Sets the 'cacheable' mode for included formatters.
792     *
793     * <p>If this is set to false, formatters will never be included in cacheable mode, otherwise they will
794     * only be included in cacheable mode in the Online project.
795     *
796     * @param cacheable the cacheable mode (true or false)
797     */
798    public void setCacheable(String cacheable) {
799
800        m_cacheable = Boolean.parseBoolean(cacheable);
801    }
802
803    /**
804     * Sets if this container should only be displayed on detail pages.<p>
805     *
806     * @param detailOnly if this container should only be displayed on detail pages
807     */
808    public void setDetailonly(String detailOnly) {
809
810        m_detailOnly = Boolean.parseBoolean(detailOnly);
811    }
812
813    /**
814     * Sets if the current container is target of detail views.<p>
815     *
816     * @param detailView <code>true</code> or <code>false</code>
817     */
818    public void setDetailview(String detailView) {
819
820        m_detailView = Boolean.parseBoolean(detailView);
821    }
822
823    /**
824     * Sets the editable by tag attribute.<p>
825     *
826     * @param editableBy the editable by tag attribute to set
827     */
828    public void setEditableby(String editableBy) {
829
830        m_editableBy = editableBy;
831    }
832
833    /**
834     * Sets the maxElements attribute value.<p>
835     *
836     * @param maxElements the maxElements value to set
837     */
838    public void setMaxElements(String maxElements) {
839
840        m_maxElements = maxElements;
841    }
842
843    /**
844     * Sets the name attribute value.<p>
845     *
846     * @param name the name value to set
847     */
848    public void setName(String name) {
849
850        m_name = name;
851    }
852
853    /**
854     * Sets the name prefix.<p>
855     *
856     * @param namePrefix the name prefix to set
857     */
858    public void setNameprefix(String namePrefix) {
859
860        m_namePrefix = namePrefix;
861    }
862
863    /**
864     * Sets the container parameter.<p>
865     *
866     * This is useful for a dynamically generated nested container,
867     * to pass information to the formatter used inside that container.
868     *
869     * @param param the parameter String to set
870     */
871    public void setParam(String param) {
872
873        m_param = param;
874    }
875
876    /**
877     * Sets the setting presets.<p>
878     *
879     * @param presets a map with string keys and values, or null
880     */
881    @SuppressWarnings("unchecked")
882    public void setSettings(Object presets) {
883
884        if (presets == null) {
885            m_settingPresets = null;
886        } else if (!(presets instanceof Map)) {
887            throw new IllegalArgumentException(
888                "cms:container -- value of 'settings' attribute  should be a map, but is "
889                    + ClassUtils.getCanonicalName(presets));
890        } else {
891            m_settingPresets = new HashMap<>((Map<String, String>)presets);
892        }
893    }
894
895    /**
896     * Sets the tag attribute.<p>
897     *
898     * @param tag the createTag to set
899     */
900    public void setTag(String tag) {
901
902        m_tag = tag;
903    }
904
905    /**
906     * Sets the tag class attribute.<p>
907     *
908     * @param tagClass the tag class attribute to set
909     */
910    public void setTagClass(String tagClass) {
911
912        m_tagClass = tagClass;
913    }
914
915    /**
916     * Sets the type attribute value.<p>
917     *
918     * @param type the type value to set
919     */
920    public void setType(String type) {
921
922        m_type = type;
923    }
924
925    /**
926     * Sets the container width as a string.<p>
927     *
928     * @param width the container width as a string
929     */
930    public void setWidth(String width) {
931
932        m_width = width;
933    }
934
935    /**
936     * Returns the serialized data of the given container.<p>
937     *
938     * @param cms the cms context
939     * @param maxElements the maximum number of elements allowed within this container
940     * @param isDetailView <code>true</code> if this container is currently being used for the detail view
941     * @param isDetailOnly <code>true</code> if this is a detail only container
942     *
943     * @return the serialized container data
944     */
945    protected String getContainerData(CmsObject cms, int maxElements, boolean isDetailView, boolean isDetailOnly) {
946
947        int width = -1;
948        try {
949            if (getWidth() != null) {
950                width = Integer.parseInt(getWidth());
951            }
952        } catch (NumberFormatException e) {
953            //ignore; set width to -1
954            LOG.debug("Error parsing container width.", e);
955        }
956        CmsContainer cont = new CmsContainer(
957            getName(),
958            getType(),
959            m_bodyContent,
960            width,
961            maxElements,
962            m_detailView,
963            isDetailView,
964            !m_hasModelGroupAncestor && isEditable(cms),
965            null,
966            m_parentContainer != null ? m_parentContainer.getName() : null,
967            m_parentElement != null ? m_parentElement.getInstanceId() : null,
968            m_settingPresets);
969        cont.setDetailOnly(isDetailOnly);
970        String result = "";
971        try {
972            result = CmsContainerpageService.getSerializedContainerInfo(cont);
973        } catch (Exception e) {
974            LOG.error(e.getLocalizedMessage(), e);
975        }
976
977        return result;
978    }
979
980    /**
981     * Returns if the container is editable by the current user.<p>
982     *
983     * @param cms the cms context
984     *
985     * @return <code>true</code> if the container is editable by the current user
986     */
987    protected boolean isEditable(CmsObject cms) {
988
989        boolean result = false;
990        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_editableBy)) {
991            String[] principals = m_editableBy.split(",");
992            List<CmsGroup> groups = null;
993            for (int i = 0; i < principals.length; i++) {
994                String key = principals[i];
995                // get the principal name from the principal String
996                String principal = key.substring(key.indexOf('.') + 1, key.length());
997
998                if (CmsGroup.hasPrefix(key)) {
999                    // read the group
1000                    principal = OpenCms.getImportExportManager().translateGroup(principal);
1001                    try {
1002                        CmsGroup group = cms.readGroup(principal);
1003                        if (groups == null) {
1004                            try {
1005                                groups = cms.getGroupsOfUser(cms.getRequestContext().getCurrentUser().getName(), false);
1006                            } catch (Exception ex) {
1007                                if (LOG.isErrorEnabled()) {
1008                                    LOG.error(ex.getLocalizedMessage(), ex);
1009                                }
1010                                groups = Collections.emptyList();
1011                            }
1012                        }
1013                        result = groups.contains(group);
1014                    } catch (CmsException e) {
1015                        if (LOG.isErrorEnabled()) {
1016                            LOG.error(e.getLocalizedMessage(), e);
1017                        }
1018                    }
1019                } else if (CmsUser.hasPrefix(key)) {
1020                    // read the user
1021                    principal = OpenCms.getImportExportManager().translateUser(principal);
1022                    try {
1023                        result = cms.getRequestContext().getCurrentUser().equals(cms.readUser(principal));
1024                    } catch (CmsException e) {
1025                        if (LOG.isErrorEnabled()) {
1026                            LOG.error(e.getLocalizedMessage(), e);
1027                        }
1028                    }
1029                } else if (CmsRole.hasPrefix(key)) {
1030                    // read the role with role name
1031                    CmsRole role = CmsRole.valueOfRoleName(principal);
1032                    if (role == null) {
1033                        // try to read the role in the old fashion with group name
1034                        role = CmsRole.valueOfGroupName(principal);
1035                    }
1036                    if (role != null) {
1037                        result = OpenCms.getRoleManager().hasRole(
1038                            cms,
1039                            role.forOrgUnit(cms.getRequestContext().getCurrentUser().getOuFqn()));
1040                    }
1041                }
1042                if (result) {
1043                    break;
1044                }
1045            }
1046        } else {
1047            result = OpenCms.getRoleManager().hasRole(cms, CmsRole.ELEMENT_AUTHOR);
1048        }
1049        return result;
1050    }
1051
1052    /**
1053     * Returns true if this is a nested container.<p>
1054     *
1055     * @return true if this is a nested container
1056     */
1057    protected boolean isNested() {
1058
1059        return (m_parentContainer != null) && (m_parentElement != null);
1060    }
1061
1062    /**
1063     * Prints the closing tag for an element wrapper if in online mode.<p>
1064     *
1065     * @param isGroupcontainer <code>true</code> if element is a group-container
1066     *
1067     * @throws IOException if the output fails
1068     */
1069    protected void printElementWrapperTagEnd(boolean isGroupcontainer) throws IOException {
1070
1071        if (m_editableRequest) {
1072            String result;
1073            if (isGroupcontainer) {
1074                result = "</div>";
1075            } else {
1076                result = "<div class=\""
1077                    + CmsContainerElement.CLASS_CONTAINER_ELEMENT_END_MARKER
1078                    + "\" style=\"display:none\"></div>";
1079            }
1080            pageContext.getOut().print(result);
1081        }
1082    }
1083
1084    /**
1085     * Prints the opening element wrapper tag for the container page editor if we are in Offline mode.<p>
1086     *
1087     * @param cms the Cms context
1088     * @param elementBean the element bean
1089     * @param page the container page
1090     * @param isGroupContainer true if the element is a group-container
1091     *
1092     * @throws Exception if something goes wrong
1093     */
1094    protected void printElementWrapperTagStart(
1095        CmsObject cms,
1096        CmsContainerElementBean elementBean,
1097        CmsContainerPageBean page,
1098        boolean isGroupContainer)
1099    throws Exception {
1100
1101        if (m_editableRequest) {
1102            StringBuffer result = new StringBuffer("<div class='");
1103            if (isGroupContainer) {
1104                result.append(CmsContainerElement.CLASS_GROUP_CONTAINER_ELEMENT_MARKER);
1105            } else {
1106                result.append(CmsContainerElement.CLASS_CONTAINER_ELEMENT_START_MARKER);
1107            }
1108            String serializedElement = getElementInfo(cms, elementBean, page);
1109            result.append("'");
1110            result.append(" " + CmsGwtConstants.ATTR_DATA_ELEMENT + "='").append(serializedElement);
1111            if (isGroupContainer) {
1112                result.append("'>");
1113            } else {
1114                result.append("' style='display:none;'></div>");
1115            }
1116            pageContext.getOut().print(result);
1117        }
1118    }
1119
1120    /**
1121     * Generates the detail view element.<p>
1122     *
1123     * @param request the current request
1124     * @param cms the CMS context
1125     * @param detailContent the detail content resource
1126     * @param container the container
1127     *
1128     * @return the detail view element
1129     */
1130    private CmsContainerElementBean generateDetailViewElement(
1131        ServletRequest request,
1132        CmsObject cms,
1133        CmsResource detailContent,
1134        CmsContainerBean container) {
1135
1136        CmsContainerElementBean element = null;
1137        if (detailContent != null) {
1138            // get the right formatter
1139
1140            CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache(
1141                cms,
1142                cms.getRequestContext().getRootUri());
1143            CmsFormatterConfiguration formatters = config.getFormatters(cms, detailContent);
1144            I_CmsFormatterBean formatter = formatters.getDetailFormatter(getType(), getContainerWidth());
1145
1146            if (formatter != null) {
1147                // use structure id as the instance id to enable use of nested containers
1148                Map<String, String> settings = new HashMap<String, String>();
1149                for (CmsContainerElementBean el : container.getElements()) {
1150                    try {
1151                        el.initResource(cms);
1152                        if (el.getResource().getTypeId() == detailContent.getTypeId()) {
1153                            settings.putAll(el.getIndividualSettings());
1154                            break;
1155                        }
1156                    } catch (CmsException e) {
1157                        LOG.error(e.getLocalizedMessage(), e);
1158                    }
1159                }
1160
1161                String formatterKey = CmsFormatterConfig.getSettingsKeyForContainer(container.getName());
1162                if (settings.containsKey(formatterKey)) {
1163                    String formatterConfigId = settings.get(formatterKey);
1164                    I_CmsFormatterBean dynamicFmt = config.findFormatter(formatterConfigId);
1165                    if (dynamicFmt != null) {
1166                        formatter = dynamicFmt;
1167                    }
1168                }
1169                settings.put(formatterKey, formatter.getKeyOrId());
1170                settings.put(CmsContainerElement.ELEMENT_INSTANCE_ID, new CmsUUID().toString());
1171                // create element bean
1172                element = new CmsContainerElementBean(
1173                    detailContent.getStructureId(),
1174                    formatter.getJspStructureId(),
1175                    settings,
1176                    false);
1177                String pageRootPath = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri());
1178                element = CmsTemplateMapper.get(request).transformDetailElement(cms, element, pageRootPath);
1179            }
1180        }
1181        return element;
1182    }
1183
1184    /**
1185     * Gets the container width as a number.<p>
1186     *
1187     * If the container width is not set, or not a number, -1 will be returned.<p>
1188     *
1189     * @return the container width or -1
1190     */
1191    private int getContainerWidth() {
1192
1193        int containerWidth = -1;
1194        try {
1195            containerWidth = Integer.parseInt(m_width);
1196        } catch (NumberFormatException e) {
1197            // do nothing, set width to -1
1198            LOG.debug("Error parsing container width.", e);
1199        }
1200        return containerWidth;
1201    }
1202
1203    /**
1204     * Returns the detail function element.<p>
1205     *
1206     * @param cms the cms context
1207     * @param detailFunctionPage the detail function page
1208     * @param req the current request
1209     *
1210     * @return the detail function element, if available
1211     */
1212    private CmsContainerElementBean getDetailFunctionElement(
1213        CmsObject cms,
1214        CmsResource detailFunctionPage,
1215        ServletRequest req) {
1216
1217        try {
1218            CmsXmlContainerPage xmlContainerPage = CmsXmlContainerPageFactory.unmarshal(cms, detailFunctionPage, req);
1219
1220            CmsContainerPageBean page = xmlContainerPage.getContainerPage(cms);
1221            CmsContainerBean container = page.getContainers().get(DETAIL_FUNCTION_CONTAINER_NAME);
1222            if (container == null) {
1223                for (Entry<String, CmsContainerBean> entry : page.getContainers().entrySet()) {
1224                    if (entry.getKey().endsWith("-" + DETAIL_FUNCTION_CONTAINER_NAME)) {
1225                        container = entry.getValue();
1226                        break;
1227                    }
1228                }
1229            }
1230            if (container != null) {
1231                return container.getElements().get(0);
1232            }
1233        } catch (CmsException e) {
1234            LOG.error(e.getLocalizedMessage(), e);
1235        }
1236        return null;
1237    }
1238
1239    /**
1240     * Returns the serialized element data.<p>
1241     *
1242     * @param cms the current cms context
1243     * @param elementBean the element to serialize
1244     * @param page the container page
1245     *
1246     * @return the serialized element data
1247     *
1248     * @throws Exception if something goes wrong
1249     */
1250    private String getElementInfo(CmsObject cms, CmsContainerElementBean elementBean, CmsContainerPageBean page)
1251    throws Exception {
1252
1253        return CmsContainerpageService.getSerializedElementInfo(
1254            cms,
1255            (HttpServletRequest)pageContext.getRequest(),
1256            (HttpServletResponse)pageContext.getResponse(),
1257            elementBean,
1258            page);
1259    }
1260
1261    /**
1262     * Parses the maximum element number from the current container and returns the resulting number.<p>
1263     *
1264     * @param requestUri the requested URI
1265     *
1266     * @return the maximum number of elements of the container
1267     */
1268    private int getMaxElements(String requestUri) {
1269
1270        String containerMaxElements = getMaxElements();
1271
1272        int maxElements = -1;
1273        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(containerMaxElements)) {
1274            try {
1275                maxElements = Integer.parseInt(containerMaxElements);
1276            } catch (NumberFormatException e) {
1277                throw new CmsIllegalStateException(
1278                    Messages.get().container(
1279                        Messages.LOG_WRONG_CONTAINER_MAXELEMENTS_3,
1280                        new Object[] {requestUri, getName(), containerMaxElements}),
1281                    e);
1282            }
1283        } else {
1284            if (LOG.isWarnEnabled()) {
1285                LOG.warn(
1286                    Messages.get().getBundle().key(
1287                        Messages.LOG_MAXELEMENTS_NOT_SET_2,
1288                        new Object[] {getName(), requestUri}));
1289            }
1290        }
1291        return maxElements;
1292    }
1293
1294    /**
1295     * Returns the ADE session cache for container elements in case of an editable request, otherwise <code>null</code>.<p>
1296     *
1297     * @param cms the cms context
1298     *
1299     * @return the session cache
1300     */
1301    private CmsADESessionCache getSessionCache(CmsObject cms) {
1302
1303        return m_editableRequest
1304        ? CmsADESessionCache.getCache((HttpServletRequest)(pageContext.getRequest()), cms)
1305        : null;
1306    }
1307
1308    /**
1309     * Evaluates if this container is nested within a model group.<p>
1310     *
1311     * @param standardContext the standard context
1312     *
1313     * @return <code>true</code> if the container has model group ancestors
1314     */
1315    private boolean hasModelGroupAncestor(CmsJspStandardContextBean standardContext) {
1316
1317        boolean result = false;
1318        if (!standardContext.isModelGroupPage()) {
1319            CmsContainerElementWrapper parent = standardContext.getElement();
1320            while ((parent != null) && !result) {
1321                result = parent.isModelGroup();
1322                parent = parent.getParent();
1323            }
1324        }
1325        return result;
1326    }
1327
1328    /**
1329     * Prints an element error tag to the response out.<p>
1330     *
1331     * @param elementSitePath the element site path
1332     * @param formatterSitePath the formatter site path
1333     * @param exception the exception causing the error
1334     *
1335     * @throws IOException if something goes wrong writing to response out
1336     */
1337    private void printElementErrorTag(String elementSitePath, String formatterSitePath, Exception exception)
1338    throws IOException {
1339
1340        if (m_editableRequest) {
1341            String stacktrace = CmsException.getStackTraceAsString(exception);
1342            if (CmsStringUtil.isEmptyOrWhitespaceOnly(stacktrace)) {
1343                stacktrace = null;
1344            } else {
1345                // stacktrace = CmsStringUtil.escapeJavaScript(stacktrace);
1346                stacktrace = CmsEncoder.escapeXml(stacktrace);
1347            }
1348            StringBuffer errorBox = new StringBuffer(256);
1349            errorBox.append(
1350                "<div style=\"display:block; padding: 5px; border: red solid 2px; color: black; background: white;\" class=\"");
1351            errorBox.append(CmsContainerElement.CLASS_ELEMENT_ERROR);
1352            errorBox.append("\">");
1353            errorBox.append(
1354                Messages.get().getBundle().key(
1355                    Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1356                    elementSitePath,
1357                    formatterSitePath));
1358            errorBox.append("<br />");
1359            errorBox.append(exception.getLocalizedMessage());
1360            if (stacktrace != null) {
1361                errorBox.append(
1362                    "<span onclick=\"opencms.openStacktraceDialog(event);\" style=\"border: 1px solid black; cursor: pointer;\">");
1363                errorBox.append(Messages.get().getBundle().key(Messages.GUI_LABEL_STACKTRACE_0));
1364                String title = Messages.get().getBundle().key(
1365                    Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1366                    elementSitePath,
1367                    formatterSitePath);
1368                errorBox.append("<span title=\"");
1369                errorBox.append(CmsEncoder.escapeXml(title));
1370                errorBox.append("\" class=\"hiddenStacktrace\" style=\"display:none;\">");
1371                errorBox.append(stacktrace);
1372                errorBox.append("</span></span>");
1373            }
1374            errorBox.append("</div>");
1375            pageContext.getOut().print(errorBox.toString());
1376        }
1377    }
1378
1379    /**
1380     * Renders a container element.<p>
1381     *
1382     * @param request the current request
1383     * @param cms the CMS context
1384     * @param standardContext the current standard contxt bean
1385     * @param element the container element to render
1386     * @param locale the requested locale
1387     * @param alreadyFull if true, only render invisible elements (they don't count towards the "max elements")
1388     *
1389     * @return true if an element was rendered that counts towards the container's maximum number of elements
1390     *
1391     * @throws Exception if something goes wrong
1392     */
1393    private boolean renderContainerElement(
1394        HttpServletRequest request,
1395        CmsObject cms,
1396        CmsJspStandardContextBean standardContext,
1397        CmsContainerElementBean element,
1398        Locale locale,
1399        boolean alreadyFull)
1400    throws Exception {
1401
1402        CmsTemplateContext context = (CmsTemplateContext)(request.getAttribute(
1403            CmsTemplateContextManager.ATTR_TEMPLATE_CONTEXT));
1404        if ((context == null) && alreadyFull) {
1405            return false;
1406        }
1407        String contextKey = null;
1408        if (context != null) {
1409            contextKey = context.getKey();
1410        } else {
1411            String rpcContextOverride = (String)request.getAttribute(
1412                CmsTemplateContextManager.ATTR_RPC_CONTEXT_OVERRIDE);
1413            contextKey = rpcContextOverride;
1414        }
1415        boolean ignoreTemplateContexts = false;
1416        try {
1417            I_CmsTemplateContextProvider templateProvider = null;
1418            if (context != null) {
1419                templateProvider = context.getProvider();
1420            }
1421            if (templateProvider == null) {
1422                templateProvider = OpenCms.getTemplateContextManager().getTemplateContextProvider(
1423                    cms,
1424                    cms.getRequestContext().getUri());
1425            }
1426            ignoreTemplateContexts = (templateProvider != null) && templateProvider.isIgnoreTemplateContextsSetting();
1427        } catch (CmsException e) {
1428            LOG.info(e.getLocalizedMessage(), e);
1429        }
1430        boolean showInContext = ignoreTemplateContexts
1431            || shouldShowInContext(element, context != null ? context.getKey() : null);
1432        boolean isOnline = cms.getRequestContext().getCurrentProject().isOnlineProject();
1433        if (!m_editableRequest && !showInContext) {
1434            return false;
1435        }
1436        try {
1437            element.initResource(cms);
1438        } catch (CmsPermissionViolationException e) {
1439            LOG.info(e.getLocalizedMessage(), e);
1440            return false;
1441        }
1442        if (!m_editableRequest && !element.isReleasedAndNotExpired()) {
1443            // do not render expired resources for the online project
1444            return false;
1445        }
1446        ServletRequest req = pageContext.getRequest();
1447        ServletResponse res = pageContext.getResponse();
1448        String containerType = getType();
1449        int containerWidth = getContainerWidth();
1450        CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfigurationWithCache(
1451            cms,
1452            cms.getRequestContext().getRootUri());
1453        boolean isGroupContainer = element.isGroupContainer(cms);
1454        boolean isInheritedContainer = element.isInheritedContainer(cms);
1455        I_CmsFormatterBean formatterConfig = null;
1456        if (!isGroupContainer && !isInheritedContainer) {
1457            // ensure that the formatter configuration id is added to the element settings, so it will be persisted on save
1458            formatterConfig = ensureValidFormatterSettings(
1459                cms,
1460                element,
1461                adeConfig,
1462                getName(),
1463                containerType,
1464                containerWidth);
1465            element.initSettings(cms, adeConfig, formatterConfig, locale, request, m_settingPresets);
1466        }
1467        // writing elements to the session cache to improve performance of the container-page editor in offline project
1468        if (m_editableRequest) {
1469            getSessionCache(cms).setCacheContainerElement(element.editorHash(), element);
1470        }
1471
1472        if (isGroupContainer || isInheritedContainer) {
1473            if (alreadyFull) {
1474                return false;
1475            }
1476            List<CmsContainerElementBean> subElements;
1477            if (isGroupContainer) {
1478                subElements = getGroupContainerElements(cms, element, req, containerType);
1479            } else {
1480                // inherited container case
1481                subElements = getInheritedContainerElements(cms, element);
1482            }
1483            // wrapping the elements with DIV containing initial element data. To be removed by the container-page editor
1484            printElementWrapperTagStart(cms, element, standardContext.getPage(), true);
1485            for (CmsContainerElementBean subelement : subElements) {
1486
1487                try {
1488                    subelement.initResource(cms);
1489                    boolean shouldShowSubElementInContext = ignoreTemplateContexts
1490                        || shouldShowInContext(subelement, contextKey);
1491                    if (!m_editableRequest
1492                        && (!shouldShowSubElementInContext || !subelement.isReleasedAndNotExpired())) {
1493                        continue;
1494                    }
1495                    I_CmsFormatterBean subElementFormatterConfig = ensureValidFormatterSettings(
1496                        cms,
1497                        subelement,
1498                        adeConfig,
1499                        getName(),
1500                        containerType,
1501                        containerWidth);
1502                    subelement.initSettings(
1503                        cms,
1504                        adeConfig,
1505                        subElementFormatterConfig,
1506                        locale,
1507                        request,
1508                        m_settingPresets);
1509                    // writing elements to the session cache to improve performance of the container-page editor
1510                    if (m_editableRequest) {
1511                        getSessionCache(cms).setCacheContainerElement(subelement.editorHash(), subelement);
1512                    }
1513                    if (subElementFormatterConfig == null) {
1514                        if (LOG.isErrorEnabled()) {
1515                            LOG.error(
1516                                new CmsIllegalStateException(
1517                                    Messages.get().container(
1518                                        Messages.ERR_XSD_NO_TEMPLATE_FORMATTER_3,
1519                                        subelement.getSitePath(),
1520                                        OpenCms.getResourceManager().getResourceType(
1521                                            subelement.getResource()).getTypeName(),
1522                                        containerType)));
1523                        }
1524                        // skip this element, it has no formatter for this container type defined
1525                        continue;
1526                    }
1527                    // execute the formatter JSP for the given element URI
1528                    // wrapping the elements with DIV containing initial element data. To be removed by the container-page editor
1529                    printElementWrapperTagStart(cms, subelement, standardContext.getPage(), false);
1530                    standardContext.setElement(subelement);
1531                    try {
1532                        String formatterSitePath;
1533                        try {
1534                            CmsResource formatterResource = cms.readResource(
1535                                subElementFormatterConfig.getJspStructureId());
1536                            formatterSitePath = cms.getSitePath(formatterResource);
1537                        } catch (CmsVfsResourceNotFoundException ex) {
1538                            LOG.debug("Formatter JSP not found by id, try using path.", ex);
1539                            formatterSitePath = cms.getRequestContext().removeSiteRoot(
1540                                subElementFormatterConfig.getJspRootPath());
1541                        }
1542                        if (shouldShowSubElementInContext) {
1543                            CmsJspTagInclude.includeTagAction(
1544                                pageContext,
1545                                formatterSitePath,
1546                                null,
1547                                locale,
1548                                false,
1549                                isOnline && m_cacheable,
1550                                null,
1551                                CmsRequestUtil.getAttributeMap(req),
1552                                req,
1553                                res);
1554                        } else {
1555                            pageContext.getOut().print(DUMMY_ELEMENT);
1556                        }
1557                    } catch (Exception e) {
1558                        if (LOG.isErrorEnabled()) {
1559                            if (CmsJspLoader.isJasperCompilerException(e)) {
1560                                LOG.error(
1561                                    Messages.get().getBundle().key(
1562                                        Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1563                                        subelement.getSitePath(),
1564                                        subElementFormatterConfig) + "\n" + e.getMessage());
1565                                LOG.debug("Full stack trace for error", e);
1566                            } else {
1567                                LOG.error(
1568                                    Messages.get().getBundle().key(
1569                                        Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1570                                        subelement.getSitePath(),
1571                                        subElementFormatterConfig),
1572                                    e);
1573                            }
1574                        }
1575                        printElementErrorTag(subelement.getSitePath(), subElementFormatterConfig.getJspRootPath(), e);
1576                    }
1577                    printElementWrapperTagEnd(false);
1578                } catch (Exception e) {
1579                    if (LOG.isErrorEnabled()) {
1580                        LOG.error(e.getLocalizedMessage(), e);
1581                    }
1582                }
1583            }
1584            printElementWrapperTagEnd(true);
1585            return true;
1586        } else {
1587            boolean result = true;
1588            if (alreadyFull && showInContext) {
1589                result = false;
1590            } else {
1591                String formatter = null;
1592                try {
1593                    if (formatterConfig != null) {
1594                        try {
1595                            CmsResource formatterResource = cms.readResource(formatterConfig.getJspStructureId());
1596                            formatter = cms.getSitePath(formatterResource);
1597                        } catch (CmsVfsResourceNotFoundException ex) {
1598                            LOG.debug("Formatter JSP not found by id, try using path.", ex);
1599                            if (cms.existsResource(
1600                                cms.getRequestContext().removeSiteRoot(formatterConfig.getJspRootPath()))) {
1601                                formatter = cms.getRequestContext().removeSiteRoot(formatterConfig.getJspRootPath());
1602                            }
1603                        }
1604                    } else {
1605                        formatter = cms.getSitePath(cms.readResource(element.getFormatterId()));
1606                    }
1607                } catch (CmsException e) {
1608                    LOG.debug("Formatter resource can not be found, try reading it from the configuration.", e);
1609                    // the formatter resource can not be found, try reading it form the configuration
1610                    CmsFormatterConfiguration elementFormatters = adeConfig.getFormatters(cms, element.getResource());
1611                    I_CmsFormatterBean elementFormatterBean = elementFormatters.getDefaultFormatter(
1612                        containerType,
1613                        containerWidth);
1614                    if (elementFormatterBean == null) {
1615                        if (LOG.isErrorEnabled()) {
1616                            LOG.error(
1617                                new CmsIllegalStateException(
1618                                    Messages.get().container(
1619                                        Messages.ERR_XSD_NO_TEMPLATE_FORMATTER_3,
1620                                        element.getSitePath(),
1621                                        OpenCms.getResourceManager().getResourceType(
1622                                            element.getResource()).getTypeName(),
1623                                        containerType)));
1624                        }
1625                        // skip this element, it has no formatter for this container type defined
1626                        return false;
1627                    }
1628                    try {
1629                        CmsResource formatterResource = cms.readResource(elementFormatterBean.getJspStructureId());
1630                        formatter = cms.getSitePath(formatterResource);
1631                    } catch (CmsVfsResourceNotFoundException ex) {
1632                        LOG.debug("Formatter JSP not found by id, try using path.", ex);
1633                        formatter = cms.getRequestContext().removeSiteRoot(elementFormatterBean.getJspRootPath());
1634                    }
1635                }
1636
1637                printElementWrapperTagStart(cms, element, standardContext.getPage(), false);
1638                standardContext.setElement(element);
1639                try {
1640                    if (!showInContext) {
1641                        // write invisible dummy element
1642                        pageContext.getOut().print(DUMMY_ELEMENT_START);
1643                        result = false;
1644                    }
1645                    try {
1646                        // execute the formatter jsp for the given element uri
1647                        CmsJspTagInclude.includeTagAction(
1648                            pageContext,
1649                            formatter,
1650                            null,
1651                            locale,
1652                            false,
1653                            isOnline && m_cacheable,
1654                            null,
1655                            CmsRequestUtil.getAtrributeMap(req),
1656                            req,
1657                            res);
1658                    } finally {
1659                        if (!showInContext) {
1660                            pageContext.getOut().print(DUMMY_ELEMENT_END);
1661                        }
1662                    }
1663                } catch (Exception e) {
1664                    if (LOG.isErrorEnabled()) {
1665                        if (CmsJspLoader.isJasperCompilerException(e)) {
1666                            LOG.error(
1667                                Messages.get().getBundle().key(
1668                                    Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1669                                    element.getSitePath(),
1670                                    formatter) + "\n" + e.getMessage());
1671                            LOG.debug("Full stack trace for error", e);
1672                        } else {
1673                            LOG.error(
1674                                Messages.get().getBundle().key(
1675                                    Messages.ERR_CONTAINER_PAGE_ELEMENT_RENDER_ERROR_2,
1676                                    element.getSitePath(),
1677                                    formatter),
1678                                e);
1679                        }
1680                    }
1681                    printElementErrorTag(element.getSitePath(), formatter, e);
1682                }
1683                printElementWrapperTagEnd(false);
1684            }
1685            return result;
1686        }
1687    }
1688
1689    /**
1690     * Resets the tag instance and standard context state.<p>
1691     */
1692    private void resetState() {
1693
1694        // clear all members so the tag object may be reused
1695        m_type = null;
1696        m_name = null;
1697        m_param = null;
1698        m_maxElements = null;
1699        m_tag = null;
1700        m_tagClass = null;
1701        m_detailView = false;
1702        m_detailOnly = false;
1703        m_width = null;
1704        m_editableBy = null;
1705        m_bodyContent = null;
1706        m_hasModelGroupAncestor = false;
1707        // reset the current element
1708        CmsJspStandardContextBean cmsContext = CmsJspStandardContextBean.getInstance(pageContext.getRequest());
1709        cmsContext.setElement(m_parentElement);
1710        cmsContext.setContainer(m_parentContainer);
1711        m_parentElement = null;
1712        m_parentContainer = null;
1713    }
1714
1715    /**
1716     * Helper method to determine whether an element should be shown in a context.<p>
1717     *
1718     * @param element the element for which the visibility should be determined
1719     * @param contextKey the key of the context for which to check
1720     *
1721     * @return true if the current context doesn't prohibit the element from being shown
1722     */
1723    private boolean shouldShowInContext(CmsContainerElementBean element, String contextKey) {
1724
1725        if (contextKey == null) {
1726            return true;
1727        }
1728
1729        try {
1730            if ((element.getResource() != null)
1731                && !OpenCms.getTemplateContextManager().shouldShowType(
1732                    contextKey,
1733                    OpenCms.getResourceManager().getResourceType(element.getResource().getTypeId()).getTypeName())) {
1734                return false;
1735            }
1736        } catch (CmsLoaderException e) {
1737            // ignore and log
1738            LOG.error(e.getLocalizedMessage(), e);
1739        }
1740        Map<String, String> settings = element.getSettings();
1741        if (settings == null) {
1742            return true;
1743        }
1744        String contextsAllowed = settings.get(CmsTemplateContextInfo.SETTING);
1745        if (contextsAllowed == null) {
1746            return true;
1747        }
1748        if (contextsAllowed.equals(CmsTemplateContextInfo.EMPTY_VALUE)) {
1749            return false;
1750        }
1751
1752        List<String> contextsAllowedList = CmsStringUtil.splitAsList(contextsAllowed, "|");
1753        if (!contextsAllowedList.contains(contextKey)) {
1754            return false;
1755        }
1756        return true;
1757    }
1758}