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