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.xml.containerpage;
029
030import org.opencms.ade.containerpage.CmsModelGroupHelper;
031import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.i18n.CmsEncoder;
037import org.opencms.i18n.CmsLocaleManager;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.relations.CmsLink;
042import org.opencms.relations.CmsRelationType;
043import org.opencms.util.CmsMacroResolver;
044import org.opencms.util.CmsUUID;
045import org.opencms.xml.CmsXmlContentDefinition;
046import org.opencms.xml.CmsXmlException;
047import org.opencms.xml.CmsXmlGenericWrapper;
048import org.opencms.xml.CmsXmlUtils;
049import org.opencms.xml.content.CmsXmlContent;
050import org.opencms.xml.content.CmsXmlContentMacroVisitor;
051import org.opencms.xml.content.CmsXmlContentProperty;
052import org.opencms.xml.content.CmsXmlContentPropertyHelper;
053import org.opencms.xml.page.CmsXmlPage;
054import org.opencms.xml.types.CmsXmlNestedContentDefinition;
055import org.opencms.xml.types.CmsXmlVfsFileValue;
056import org.opencms.xml.types.I_CmsXmlContentValue;
057import org.opencms.xml.types.I_CmsXmlSchemaType;
058
059import java.util.ArrayList;
060import java.util.Arrays;
061import java.util.Collections;
062import java.util.HashMap;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.LinkedHashMap;
066import java.util.List;
067import java.util.Locale;
068import java.util.Map;
069import java.util.Set;
070
071import org.apache.commons.logging.Log;
072
073import org.dom4j.Document;
074import org.dom4j.Element;
075import org.xml.sax.EntityResolver;
076
077/**
078 * Implementation of a object used to access and manage the xml data of a container page.<p>
079 *
080 * In addition to the XML content interface. It also provides access to more comfortable beans.
081 *
082 * @since 7.5.2
083 *
084 * @see #getContainerPage(CmsObject)
085 */
086public class CmsXmlContainerPage extends CmsXmlContent {
087
088    /** XML node name constants. */
089    public enum XmlNode {
090
091        /** Container attribute node name. */
092        Attribute,
093        /** Main node name. */
094        Containers,
095        /** The create new element node name. */
096        CreateNew,
097        /** Container elements node name. */
098        Elements,
099        /** Element formatter node name. */
100        Formatter,
101        /** The is root container node name. */
102        IsRootContainer,
103        /** Container attribute key node name. */
104        Key,
105        /** Container name node name. */
106        Name,
107        /** Parent element instance id node name. */
108        ParentInstanceId,
109        /** Container type node name. */
110        Type,
111        /** Element URI node name. */
112        Uri,
113        /** Container attribute value node name. */
114        Value;
115    }
116
117    /** The log object for this class. */
118    private static final Log LOG = CmsLog.getLog(CmsXmlContainerPage.class);
119
120    /** The container page objects. */
121    private Map<Locale, CmsContainerPageBean> m_cntPages;
122
123    /**
124     * Hides the public constructor.<p>
125     */
126    protected CmsXmlContainerPage() {
127
128        // noop
129    }
130
131    /**
132     * Creates a new container page based on the provided XML document.<p>
133     *
134     * The given encoding is used when marshalling the XML again later.<p>
135     *
136     * @param cms the cms context, if <code>null</code> no link validation is performed
137     * @param document the document to create the container page from
138     * @param encoding the encoding of the container page
139     * @param resolver the XML entity resolver to use
140     */
141    protected CmsXmlContainerPage(CmsObject cms, Document document, String encoding, EntityResolver resolver) {
142
143        // must set document first to be able to get the content definition
144        m_document = document;
145        // for the next line to work the document must already be available
146        m_contentDefinition = getContentDefinition(resolver);
147        // initialize the XML content structure
148        initDocument(cms, m_document, encoding, m_contentDefinition);
149    }
150
151    /**
152     * Create a new container page based on the given default content,
153     * that will have all language nodes of the default content and ensures the presence of the given locale.<p>
154     *
155     * The given encoding is used when marshalling the XML again later.<p>
156     *
157     * @param cms the current users OpenCms content
158     * @param locale the locale to generate the default content for
159     * @param modelUri the absolute path to the container page file acting as model
160     *
161     * @throws CmsException in case the model file is not found or not valid
162     */
163    protected CmsXmlContainerPage(CmsObject cms, Locale locale, String modelUri)
164    throws CmsException {
165
166        // init model from given modelUri
167        CmsFile modelFile = cms.readFile(modelUri, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
168        CmsXmlContainerPage model = CmsXmlContainerPageFactory.unmarshal(cms, modelFile);
169
170        // initialize macro resolver to use on model file values
171        CmsMacroResolver macroResolver = CmsMacroResolver.newInstance().setCmsObject(cms);
172
173        // content definition must be set here since it's used during document creation
174        m_contentDefinition = model.getContentDefinition();
175        // get the document from the default content
176        Document document = (Document)model.m_document.clone();
177        // initialize the XML content structure
178        initDocument(cms, document, model.getEncoding(), m_contentDefinition);
179        // resolve eventual macros in the nodes
180        visitAllValuesWith(new CmsXmlContentMacroVisitor(cms, macroResolver));
181        if (!hasLocale(locale)) {
182            // required locale not present, add it
183            try {
184                addLocale(cms, locale);
185            } catch (CmsXmlException e) {
186                // this can not happen since the locale does not exist
187                LOG.error(e.getMessage(), e);
188            }
189        }
190    }
191
192    /**
193     * Create a new container page based on the given content definition,
194     * that will have one language node for the given locale all initialized with default values.<p>
195     *
196     * The given encoding is used when marshalling the XML again later.<p>
197     *
198     * @param cms the current users OpenCms content
199     * @param locale the locale to generate the default content for
200     * @param encoding the encoding to use when marshalling the container page later
201     * @param contentDefinition the content definition to create the content for
202     */
203    protected CmsXmlContainerPage(
204        CmsObject cms,
205        Locale locale,
206        String encoding,
207        CmsXmlContentDefinition contentDefinition) {
208
209        // content definition must be set here since it's used during document creation
210        m_contentDefinition = contentDefinition;
211        // create the XML document according to the content definition
212        Document document = m_contentDefinition.createDocument(cms, this, CmsLocaleManager.MASTER_LOCALE);
213        // initialize the XML content structure
214        initDocument(cms, document, encoding, m_contentDefinition);
215    }
216
217    /**
218     * Saves a container page bean to the in-memory XML structure and returns the changed content.<p>
219     *
220     * @param cms the current CMS context
221     * @param cntPage the container page bean
222     * @return the new content for the container page
223     * @throws CmsException if something goes wrong
224     */
225    public byte[] createContainerPageXml(CmsObject cms, CmsContainerPageBean cntPage) throws CmsException {
226
227        // make sure all links are validated
228        writeContainerPage(cms, cntPage);
229        checkLinkConcistency(cms);
230        return marshal();
231
232    }
233
234    /**
235     * Gets the container page content as a bean.<p>
236     *
237     * @param cms the current CMS context
238     * @return the bean containing the container page data
239     */
240    public CmsContainerPageBean getContainerPage(CmsObject cms) {
241
242        Locale masterLocale = CmsLocaleManager.MASTER_LOCALE;
243        Locale localeToLoad = null;
244        // always use master locale if possible, otherwise use the first locale.
245        // this is important for 'legacy' container pages which were created before container pages became locale independent
246        if (m_cntPages.containsKey(masterLocale)) {
247            localeToLoad = masterLocale;
248        } else if (!m_cntPages.isEmpty()) {
249            localeToLoad = m_cntPages.keySet().iterator().next();
250        }
251        if (localeToLoad == null) {
252            return null;
253        } else {
254            CmsContainerPageBean result = m_cntPages.get(localeToLoad);
255            return result;
256        }
257    }
258
259    /**
260     * @see org.opencms.xml.content.CmsXmlContent#isAutoCorrectionEnabled()
261     */
262    @Override
263    public boolean isAutoCorrectionEnabled() {
264
265        return true;
266    }
267
268    /**
269     * Saves given container page in the current locale, and not only in memory but also to VFS.<p>
270     *
271     * @param cms the current cms context
272     * @param cntPage the container page to save
273     *
274     * @throws CmsException if something goes wrong
275     */
276    public void save(CmsObject cms, CmsContainerPageBean cntPage) throws CmsException {
277
278        save(cms, cntPage, false);
279    }
280
281    /**
282     * Saves given container page in the current locale, and not only in memory but also to VFS.<p>
283     *
284     * @param cms the current cms context
285     * @param cntPage the container page to save
286     * @param ifChangedOnly <code>true</code> to only write the file if the content has changed
287     *
288     * @throws CmsException if something goes wrong
289     */
290    public void save(CmsObject cms, CmsContainerPageBean cntPage, boolean ifChangedOnly) throws CmsException {
291
292        CmsFile file = getFile();
293        byte[] data = createContainerPageXml(cms, cntPage);
294        if (ifChangedOnly && Arrays.equals(file.getContents(), data)) {
295            return;
296        }
297        // lock the file
298        cms.lockResourceTemporary(file);
299        file.setContents(data);
300        cms.writeFile(file);
301    }
302
303    /**
304     * Saves a container page in in-memory XML structure.<p>
305     *
306     * @param cms the current CMS context
307     * @param cntPage the container page bean to save
308     *
309     * @throws CmsException if something goes wrong
310     */
311    public void writeContainerPage(CmsObject cms, CmsContainerPageBean cntPage) throws CmsException {
312
313        // keep unused containers
314        CmsContainerPageBean savePage = cleanupContainersContainers(cms, cntPage);
315        savePage = removeEmptyContainers(cntPage);
316        // Replace existing locales with master locale
317        for (Locale locale : getLocales()) {
318            removeLocale(locale);
319        }
320        Locale masterLocale = CmsLocaleManager.MASTER_LOCALE;
321        addLocale(cms, masterLocale);
322
323        // add the nodes to the raw XML structure
324        Element parent = getLocaleNode(masterLocale);
325        saveContainerPage(cms, parent, savePage);
326        initDocument(m_document, m_encoding, m_contentDefinition);
327    }
328
329    /**
330     * Checks the link consistency for a given locale and reinitializes the document afterwards.<p>
331     *
332     * @param cms the cms context
333     */
334    protected void checkLinkConcistency(CmsObject cms) {
335
336        Locale masterLocale = CmsLocaleManager.MASTER_LOCALE;
337
338        for (I_CmsXmlContentValue contentValue : getValues(masterLocale)) {
339            if (contentValue instanceof CmsXmlVfsFileValue) {
340                CmsLink link = ((CmsXmlVfsFileValue)contentValue).getLink(cms);
341                link.checkConsistency(cms);
342            }
343        }
344        initDocument();
345    }
346
347    /**
348     * Removes all empty containers and merges the containers of the current document that are not used in the given container page with it.<p>
349     *
350     * @param cms the current CMS context
351     * @param cntPage the container page to merge
352     *
353     * @return a new container page with the additional unused containers
354     */
355    protected CmsContainerPageBean cleanupContainersContainers(CmsObject cms, CmsContainerPageBean cntPage) {
356
357        // get the used containers first
358        Map<String, CmsContainerBean> currentContainers = cntPage.getContainers();
359        List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>();
360        for (String cntName : cntPage.getNames()) {
361            CmsContainerBean container = currentContainers.get(cntName);
362            if (!container.getElements().isEmpty()) {
363                containers.add(container);
364            }
365        }
366
367        // now get the unused containers
368        CmsContainerPageBean currentContainerPage = getContainerPage(cms);
369        if (currentContainerPage != null) {
370            for (String cntName : currentContainerPage.getNames()) {
371                if (!currentContainers.containsKey(cntName)) {
372                    CmsContainerBean container = currentContainerPage.getContainers().get(cntName);
373                    if (!container.getElements().isEmpty()) {
374                        containers.add(container);
375                    }
376                }
377            }
378        }
379
380        // check if any nested containers have lost their parent element
381
382        // first collect all present elements
383        Map<String, CmsContainerElementBean> pageElements = new HashMap<String, CmsContainerElementBean>();
384        Map<String, String> parentContainers = new HashMap<String, String>();
385        for (CmsContainerBean container : containers) {
386            for (CmsContainerElementBean element : container.getElements()) {
387                try {
388                    element.initResource(cms);
389
390                    if (!CmsModelGroupHelper.isModelGroupResource(element.getResource())) {
391                        pageElements.put(element.getInstanceId(), element);
392                        parentContainers.put(element.getInstanceId(), container.getName());
393                    }
394                } catch (CmsException e) {
395                    LOG.warn(e.getLocalizedMessage(), e);
396                }
397            }
398        }
399        Iterator<CmsContainerBean> cntIt = containers.iterator();
400        while (cntIt.hasNext()) {
401            CmsContainerBean container = cntIt.next();
402            // check all unused nested containers if their parent element is still part of the page
403            if (!currentContainers.containsKey(container.getName())
404                && (container.isNestedContainer() && !container.isRootContainer())) {
405                boolean remove = !pageElements.containsKey(container.getParentInstanceId())
406                    || container.getElements().isEmpty();
407                if (!remove) {
408                    // check if the parent element formatter is set to strictly render all nested containers
409                    CmsContainerElementBean element = pageElements.get(container.getParentInstanceId());
410                    String settingsKey = CmsFormatterConfig.getSettingsKeyForContainer(
411                        parentContainers.get(element.getInstanceId()));
412                    String formatterId = element.getIndividualSettings().get(settingsKey);
413                    if (CmsUUID.isValidUUID(formatterId)) {
414                        I_CmsFormatterBean formatterBean = OpenCms.getADEManager().getCachedFormatters(
415                            false).getFormatters().get(new CmsUUID(formatterId));
416                        remove = (formatterBean instanceof CmsFormatterBean)
417                            && ((CmsFormatterBean)formatterBean).isStrictContainers();
418                    }
419                }
420                if (remove) {
421                    // remove the sub elements from the page list
422                    for (CmsContainerElementBean element : container.getElements()) {
423                        pageElements.remove(element.getInstanceId());
424                    }
425                    // remove the container
426                    cntIt.remove();
427                }
428            }
429        }
430
431        return new CmsContainerPageBean(containers);
432    }
433
434    /**
435     * Fills a {@link CmsXmlVfsFileValue} with the resource identified by the given id.<p>
436     *
437     * @param cms the current CMS context
438     * @param element the XML element to fill
439     * @param resourceId the ID identifying the resource to use
440     *
441     * @return the resource
442     *
443     * @throws CmsException if the resource can not be read
444     */
445    protected CmsResource fillResource(CmsObject cms, Element element, CmsUUID resourceId) throws CmsException {
446
447        String xpath = element.getPath();
448        int pos = xpath.lastIndexOf("/" + XmlNode.Containers.name() + "/");
449        if (pos > 0) {
450            xpath = xpath.substring(pos + 1);
451        }
452        CmsRelationType type = getHandler().getRelationType(xpath);
453        CmsResource res = cms.readResource(resourceId, CmsResourceFilter.IGNORE_EXPIRATION);
454        CmsXmlVfsFileValue.fillEntry(element, res.getStructureId(), res.getRootPath(), type);
455        return res;
456    }
457
458    /**
459     * @see org.opencms.xml.content.CmsXmlContent#initDocument(org.opencms.file.CmsObject, org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition)
460     */
461    @Override
462    protected void initDocument(CmsObject cms, Document document, String encoding, CmsXmlContentDefinition definition) {
463
464        m_document = document;
465        m_contentDefinition = definition;
466        m_encoding = CmsEncoder.lookupEncoding(encoding, encoding);
467        m_elementLocales = new HashMap<String, Set<Locale>>();
468        m_elementNames = new HashMap<Locale, Set<String>>();
469        m_locales = new HashSet<Locale>();
470        m_cntPages = new LinkedHashMap<Locale, CmsContainerPageBean>();
471        clearBookmarks();
472
473        // initialize the bookmarks
474        for (Iterator<Element> itCntPages = CmsXmlGenericWrapper.elementIterator(
475            m_document.getRootElement()); itCntPages.hasNext();) {
476            Element cntPage = itCntPages.next();
477
478            try {
479                Locale locale = CmsLocaleManager.getLocale(
480                    cntPage.attribute(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE).getValue());
481
482                addLocale(locale);
483
484                List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>();
485                for (Iterator<Element> itCnts = CmsXmlGenericWrapper.elementIterator(
486                    cntPage,
487                    XmlNode.Containers.name()); itCnts.hasNext();) {
488                    Element container = itCnts.next();
489
490                    // container itself
491                    int cntIndex = CmsXmlUtils.getXpathIndexInt(container.getUniquePath(cntPage));
492                    String cntPath = CmsXmlUtils.createXpathElement(container.getName(), cntIndex);
493                    I_CmsXmlSchemaType cntSchemaType = definition.getSchemaType(container.getName());
494                    I_CmsXmlContentValue cntValue = cntSchemaType.createValue(this, container, locale);
495                    addBookmark(cntPath, locale, true, cntValue);
496                    CmsXmlContentDefinition cntDef = ((CmsXmlNestedContentDefinition)cntSchemaType).getNestedContentDefinition();
497
498                    // name
499                    Element name = container.element(XmlNode.Name.name());
500                    addBookmarkForElement(name, locale, container, cntPath, cntDef);
501
502                    // type
503                    Element type = container.element(XmlNode.Type.name());
504                    addBookmarkForElement(type, locale, container, cntPath, cntDef);
505
506                    // parent instance id
507                    Element parentInstance = container.element(XmlNode.ParentInstanceId.name());
508                    if (parentInstance != null) {
509                        addBookmarkForElement(parentInstance, locale, container, cntPath, cntDef);
510                    }
511
512                    Element isRootContainer = container.element(XmlNode.IsRootContainer.name());
513                    if (isRootContainer != null) {
514                        addBookmarkForElement(isRootContainer, locale, container, cntPath, cntDef);
515                    }
516
517                    List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>();
518                    // Elements
519                    for (Iterator<Element> itElems = CmsXmlGenericWrapper.elementIterator(
520                        container,
521                        XmlNode.Elements.name()); itElems.hasNext();) {
522                        Element element = itElems.next();
523
524                        // element itself
525                        int elemIndex = CmsXmlUtils.getXpathIndexInt(element.getUniquePath(container));
526                        String elemPath = CmsXmlUtils.concatXpath(
527                            cntPath,
528                            CmsXmlUtils.createXpathElement(element.getName(), elemIndex));
529                        I_CmsXmlSchemaType elemSchemaType = cntDef.getSchemaType(element.getName());
530                        I_CmsXmlContentValue elemValue = elemSchemaType.createValue(this, element, locale);
531                        addBookmark(elemPath, locale, true, elemValue);
532                        CmsXmlContentDefinition elemDef = ((CmsXmlNestedContentDefinition)elemSchemaType).getNestedContentDefinition();
533
534                        // uri
535                        Element uri = element.element(XmlNode.Uri.name());
536                        addBookmarkForElement(uri, locale, element, elemPath, elemDef);
537                        Element uriLink = uri.element(CmsXmlPage.NODE_LINK);
538                        CmsUUID elementId = null;
539                        if (uriLink == null) {
540                            // this can happen when adding the elements node to the xml content
541                            // it is not dangerous since the link has to be set before saving
542                        } else {
543                            CmsLink link = new CmsLink(uriLink);
544                            if (cms != null) {
545                                link.checkConsistency(cms);
546                            }
547                            elementId = link.getStructureId();
548                        }
549                        Element createNewElement = element.element(XmlNode.CreateNew.name());
550                        boolean createNew = (createNewElement != null)
551                            && Boolean.parseBoolean(createNewElement.getStringValue());
552
553                        // formatter
554                        Element formatter = element.element(XmlNode.Formatter.name());
555                        addBookmarkForElement(formatter, locale, element, elemPath, elemDef);
556                        Element formatterLink = formatter.element(CmsXmlPage.NODE_LINK);
557                        CmsUUID formatterId = null;
558                        if (formatterLink == null) {
559                            // this can happen when adding the elements node to the xml content
560                            // it is not dangerous since the link has to be set before saving
561                        } else {
562                            CmsLink link = new CmsLink(formatterLink);
563                            if (cms != null) {
564                                link.checkConsistency(cms);
565                            }
566                            formatterId = link.getStructureId();
567                        }
568
569                        // the properties
570                        Map<String, String> propertiesMap = CmsXmlContentPropertyHelper.readProperties(
571                            this,
572                            locale,
573                            element,
574                            elemPath,
575                            elemDef);
576
577                        if (elementId != null) {
578                            elements.add(new CmsContainerElementBean(elementId, formatterId, propertiesMap, createNew));
579                        }
580                    }
581                    CmsContainerBean newContainerBean = new CmsContainerBean(
582                        name.getText(),
583                        type.getText(),
584                        parentInstance != null ? parentInstance.getText() : null,
585                        (isRootContainer != null) && Boolean.valueOf(isRootContainer.getText()).booleanValue(),
586                        elements);
587                    containers.add(newContainerBean);
588                }
589
590                m_cntPages.put(locale, new CmsContainerPageBean(containers));
591            } catch (NullPointerException e) {
592                LOG.error(
593                    org.opencms.xml.content.Messages.get().getBundle().key(
594                        org.opencms.xml.content.Messages.LOG_XMLCONTENT_INIT_BOOKMARKS_0),
595                    e);
596            }
597        }
598
599        if (cms != null) {
600            // this will remove all invalid links
601            getHandler().invalidateBrokenLinks(cms, this);
602        }
603    }
604
605    /**
606     * @see org.opencms.xml.A_CmsXmlDocument#initDocument(org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition)
607     */
608    @Override
609    protected void initDocument(Document document, String encoding, CmsXmlContentDefinition definition) {
610
611        initDocument(null, document, encoding, definition);
612    }
613
614    /**
615     * Removes all empty containers to clean up container page XML.<p>
616     *
617     * @param cntPage the container page bean
618     *
619     * @return the newly generated result
620     */
621    protected CmsContainerPageBean removeEmptyContainers(CmsContainerPageBean cntPage) {
622
623        List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>();
624        for (CmsContainerBean container : cntPage.getContainers().values()) {
625            if (container.getElements().size() > 0) {
626                containers.add(container);
627            }
628        }
629        return new CmsContainerPageBean(containers);
630    }
631
632    /**
633     * Adds the given container page to the given element.<p>
634     *
635     * @param cms the current CMS object
636     * @param parent the element to add it
637     * @param cntPage the container page to add
638     *
639     * @throws CmsException if something goes wrong
640     */
641    protected void saveContainerPage(CmsObject cms, Element parent, CmsContainerPageBean cntPage) throws CmsException {
642
643        parent.clearContent();
644
645        // save containers in a defined order
646        List<String> containerNames = new ArrayList<String>(cntPage.getNames());
647        Collections.sort(containerNames);
648
649        for (String containerName : containerNames) {
650            CmsContainerBean container = cntPage.getContainers().get(containerName);
651
652            // the container
653            Element cntElement = parent.addElement(XmlNode.Containers.name());
654            cntElement.addElement(XmlNode.Name.name()).addCDATA(container.getName());
655            cntElement.addElement(XmlNode.Type.name()).addCDATA(container.getType());
656            if (container.isNestedContainer()) {
657                cntElement.addElement(XmlNode.ParentInstanceId.name()).addCDATA(container.getParentInstanceId());
658            }
659            if (container.isRootContainer()) {
660                cntElement.addElement(XmlNode.IsRootContainer.name()).addText(Boolean.TRUE.toString());
661            }
662
663            // the elements
664            for (CmsContainerElementBean element : container.getElements()) {
665                Element elemElement = cntElement.addElement(XmlNode.Elements.name());
666
667                // the element
668                Element uriElem = elemElement.addElement(XmlNode.Uri.name());
669                CmsResource uriRes = fillResource(cms, uriElem, element.getId());
670                Element formatterElem = elemElement.addElement(XmlNode.Formatter.name());
671                fillResource(cms, formatterElem, element.getFormatterId());
672                if (element.isCreateNew()) {
673                    Element createNewElem = elemElement.addElement(XmlNode.CreateNew.name());
674                    createNewElem.addText(Boolean.TRUE.toString());
675                }
676                // the properties
677                Map<String, String> properties = element.getIndividualSettings();
678                Map<String, CmsXmlContentProperty> propertiesConf = OpenCms.getADEManager().getElementSettings(
679                    cms,
680                    uriRes);
681
682                CmsXmlContentPropertyHelper.saveProperties(cms, elemElement, properties, propertiesConf);
683            }
684        }
685    }
686
687    /**
688     * @see org.opencms.xml.content.CmsXmlContent#setFile(org.opencms.file.CmsFile)
689     */
690    @Override
691    protected void setFile(CmsFile file) {
692
693        // just for visibility from the factory
694        super.setFile(file);
695    }
696
697}