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