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