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, 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.shared.CmsContainer;
031import org.opencms.file.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
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.CmsStringUtil;
045import org.opencms.util.CmsUUID;
046import org.opencms.xml.CmsXmlContentDefinition;
047import org.opencms.xml.CmsXmlException;
048import org.opencms.xml.CmsXmlGenericWrapper;
049import org.opencms.xml.CmsXmlUtils;
050import org.opencms.xml.content.CmsXmlContent;
051import org.opencms.xml.content.CmsXmlContentMacroVisitor;
052import org.opencms.xml.content.CmsXmlContentProperty;
053import org.opencms.xml.content.CmsXmlContentPropertyHelper;
054import org.opencms.xml.page.CmsXmlPage;
055import org.opencms.xml.types.CmsXmlNestedContentDefinition;
056import org.opencms.xml.types.CmsXmlVfsFileValue;
057import org.opencms.xml.types.I_CmsXmlContentValue;
058import org.opencms.xml.types.I_CmsXmlSchemaType;
059
060import java.util.ArrayList;
061import java.util.HashMap;
062import java.util.HashSet;
063import java.util.Iterator;
064import java.util.List;
065import java.util.Locale;
066import java.util.Map;
067import java.util.Set;
068
069import org.apache.commons.logging.Log;
070
071import org.dom4j.Document;
072import org.dom4j.Element;
073import org.xml.sax.EntityResolver;
074
075/**
076 * Implementation of a object used to access and manage the xml data of a group container.<p>
077 *
078 * In addition to the XML content interface. It also provides access to more comfortable beans.<p>
079 *
080 * @since 8.0.0
081 */
082public class CmsXmlGroupContainer extends CmsXmlContent {
083
084    /** XML node name constants. */
085    public enum XmlNode {
086
087        /** Container description node name. */
088        Description,
089        /** Container elements node name. */
090        Element,
091        /** Main node name. */
092        GroupContainers,
093        /** Container title node name. */
094        Title,
095        /** Container type node name. */
096        Type,
097        /** File list URI node name. */
098        Uri;
099    }
100
101    /** The log object for this class. */
102    private static final Log LOG = CmsLog.getLog(CmsXmlGroupContainer.class);
103
104    /** The group container objects. */
105    private Map<Locale, CmsGroupContainerBean> m_groupContainers;
106
107    /**
108     * Hides the public constructor.<p>
109     */
110    protected CmsXmlGroupContainer() {
111
112        // do nothing
113    }
114
115    /**
116     * Creates a new group container based on the provided XML document.<p>
117     *
118     * The given encoding is used when marshalling the XML again later.<p>
119     *
120     * @param cms the cms context, if <code>null</code> no link validation is performed
121     * @param document the document to create the container page from
122     * @param encoding the encoding of the container page
123     * @param resolver the XML entity resolver to use
124     */
125    protected CmsXmlGroupContainer(CmsObject cms, Document document, String encoding, EntityResolver resolver) {
126
127        // must set document first to be able to get the content definition
128        m_document = document;
129        // for the next line to work the document must already be available
130        m_contentDefinition = getContentDefinition(resolver);
131        // initialize the XML content structure
132        initDocument(cms, m_document, encoding, m_contentDefinition);
133    }
134
135    /**
136     * Create a new group container based on the given default content,
137     * that will have all language nodes of the default content and ensures the presence of the given locale.<p>
138     *
139     * The given encoding is used when marshalling the XML again later.<p>
140     *
141     * @param cms the current users OpenCms content
142     * @param locale the locale to generate the default content for
143     * @param modelUri the absolute path to the container page file acting as model
144     *
145     * @throws CmsException in case the model file is not found or not valid
146     */
147    protected CmsXmlGroupContainer(CmsObject cms, Locale locale, String modelUri)
148    throws CmsException {
149
150        // init model from given modelUri
151        CmsFile modelFile = cms.readFile(modelUri, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
152        CmsXmlGroupContainer model = CmsXmlGroupContainerFactory.unmarshal(cms, modelFile);
153
154        // initialize macro resolver to use on model file values
155        CmsMacroResolver macroResolver = CmsMacroResolver.newInstance().setCmsObject(cms);
156
157        // content definition must be set here since it's used during document creation
158        m_contentDefinition = model.getContentDefinition();
159        // get the document from the default content
160        Document document = (Document)model.m_document.clone();
161        // initialize the XML content structure
162        initDocument(cms, document, model.getEncoding(), m_contentDefinition);
163        // resolve eventual macros in the nodes
164        visitAllValuesWith(new CmsXmlContentMacroVisitor(cms, macroResolver));
165        if (!hasLocale(locale)) {
166            // required locale not present, add it
167            try {
168                addLocale(cms, locale);
169            } catch (CmsXmlException e) {
170                // this can not happen since the locale does not exist
171            }
172        }
173    }
174
175    /**
176     * Create a new container page based on the given content definition,
177     * that will have one language node for the given locale all initialized with default values.<p>
178     *
179     * The given encoding is used when marshalling the XML again later.<p>
180     *
181     * @param cms the current users OpenCms content
182     * @param locale the locale to generate the default content for
183     * @param encoding the encoding to use when marshalling the container page later
184     * @param contentDefinition the content definition to create the content for
185     */
186    protected CmsXmlGroupContainer(
187        CmsObject cms,
188        Locale locale,
189        String encoding,
190        CmsXmlContentDefinition contentDefinition) {
191
192        // content definition must be set here since it's used during document creation
193        m_contentDefinition = contentDefinition;
194        // create the XML document according to the content definition
195        Document document = m_contentDefinition.createDocument(cms, this, locale);
196        // initialize the XML content structure
197        initDocument(cms, document, encoding, m_contentDefinition);
198    }
199
200    /**
201     * Removes all locales from the element group XML.<p>
202     *
203     * @throws CmsXmlException if something goes wrong
204     */
205    public void clearLocales() throws CmsXmlException {
206
207        for (Locale locale : getLocales()) {
208            removeLocale(locale);
209        }
210    }
211
212    /**
213     * Returns the group container bean for the given locale.<p>
214     *
215     * @param cms the cms context
216     *
217     * @return the group container bean
218     */
219    public CmsGroupContainerBean getGroupContainer(CmsObject cms) {
220
221        if (m_groupContainers.containsKey(CmsLocaleManager.MASTER_LOCALE)) {
222            return m_groupContainers.get(CmsLocaleManager.MASTER_LOCALE);
223        } else if (!m_groupContainers.isEmpty()) {
224            return m_groupContainers.get(m_groupContainers.keySet().iterator().next());
225        } else {
226            return null;
227        }
228    }
229
230    /**
231     * @see org.opencms.xml.content.CmsXmlContent#isAutoCorrectionEnabled()
232     */
233    @Override
234    public boolean isAutoCorrectionEnabled() {
235
236        return true;
237    }
238
239    /**
240     * Saves given container page in the current locale, and not only in memory but also to VFS.<p>
241     *
242     * @param cms the current cms context
243     * @param groupContainer the group-container page to save
244     * @param locale the locale to save
245     *
246     * @throws CmsException if something goes wrong
247     */
248    public void save(CmsObject cms, CmsGroupContainerBean groupContainer, Locale locale) throws CmsException {
249
250        CmsFile file = getFile();
251
252        // lock the file
253        cms.lockResourceTemporary(cms.getSitePath(file));
254
255        // wipe the locale
256        if (hasLocale(locale)) {
257            removeLocale(locale);
258        }
259
260        addLocale(cms, locale);
261
262        // add the nodes to the raw XML structure
263        Element parent = getLocaleNode(locale);
264        saveGroupContainer(cms, parent, groupContainer);
265
266        // generate bookmarks
267        initDocument(m_document, m_encoding, m_contentDefinition);
268
269        // write to VFS
270        file.setContents(marshal());
271        cms.writeFile(file);
272    }
273
274    /**
275     * Fills a {@link CmsXmlVfsFileValue} with the resource identified by the given id.<p>
276     *
277     * @param cms the current CMS context
278     * @param element the XML element to fill
279     * @param res the resource to use
280     */
281    protected void fillResource(CmsObject cms, Element element, CmsResource res) {
282
283        String xpath = element.getPath();
284        int pos = xpath.lastIndexOf("/" + XmlNode.GroupContainers.name() + "/");
285        if (pos > 0) {
286            xpath = xpath.substring(pos + 1);
287        }
288        CmsRelationType type = getHandler().getRelationType(xpath);
289        CmsXmlVfsFileValue.fillEntry(element, res.getStructureId(), res.getRootPath(), type);
290    }
291
292    /**
293     * @see org.opencms.xml.A_CmsXmlDocument#initDocument(org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition)
294     */
295    @Override
296    protected void initDocument(Document document, String encoding, CmsXmlContentDefinition definition) {
297
298        m_document = document;
299        m_contentDefinition = definition;
300        m_encoding = CmsEncoder.lookupEncoding(encoding, encoding);
301        m_elementLocales = new HashMap<String, Set<Locale>>();
302        m_elementNames = new HashMap<Locale, Set<String>>();
303        m_locales = new HashSet<Locale>();
304        m_groupContainers = new HashMap<Locale, CmsGroupContainerBean>();
305        clearBookmarks();
306
307        // initialize the bookmarks
308        for (Iterator<Element> itGroupContainers = CmsXmlGenericWrapper.elementIterator(
309            m_document.getRootElement()); itGroupContainers.hasNext();) {
310            Element cntPage = itGroupContainers.next();
311
312            try {
313                Locale locale = CmsLocaleManager.getLocale(
314                    cntPage.attribute(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE).getValue());
315
316                addLocale(locale);
317                Element groupContainer = cntPage.element(XmlNode.GroupContainers.name());
318
319                // container itself
320                int cntIndex = CmsXmlUtils.getXpathIndexInt(groupContainer.getUniquePath(cntPage));
321                String cntPath = CmsXmlUtils.createXpathElement(groupContainer.getName(), cntIndex);
322                I_CmsXmlSchemaType cntSchemaType = definition.getSchemaType(groupContainer.getName());
323                I_CmsXmlContentValue cntValue = cntSchemaType.createValue(this, groupContainer, locale);
324                addBookmark(cntPath, locale, true, cntValue);
325                CmsXmlContentDefinition cntDef = ((CmsXmlNestedContentDefinition)cntSchemaType).getNestedContentDefinition();
326
327                //title
328                Element title = groupContainer.element(XmlNode.Title.name());
329                addBookmarkForElement(title, locale, groupContainer, cntPath, cntDef);
330
331                //description
332                Element description = groupContainer.element(XmlNode.Description.name());
333                addBookmarkForElement(description, locale, groupContainer, cntPath, cntDef);
334
335                // types
336                Set<String> types = new HashSet<String>();
337                for (Iterator<Element> itTypes = CmsXmlGenericWrapper.elementIterator(
338                    groupContainer,
339                    XmlNode.Type.name()); itTypes.hasNext();) {
340                    Element type = itTypes.next();
341                    addBookmarkForElement(type, locale, groupContainer, cntPath, cntDef);
342                    String typeName = type.getTextTrim();
343                    if (!CmsStringUtil.isEmptyOrWhitespaceOnly(typeName)) {
344                        types.addAll(CmsContainer.splitType(typeName));
345                    }
346                }
347
348                List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>();
349                // Elements
350                for (Element element : CmsXmlGenericWrapper.elementIterable(groupContainer, XmlNode.Element.name())) {
351                    // element itself
352                    int elemIndex = CmsXmlUtils.getXpathIndexInt(element.getUniquePath(groupContainer));
353                    String elemPath = CmsXmlUtils.concatXpath(
354                        cntPath,
355                        CmsXmlUtils.createXpathElement(element.getName(), elemIndex));
356                    I_CmsXmlSchemaType elemSchemaType = cntDef.getSchemaType(element.getName());
357                    I_CmsXmlContentValue elemValue = elemSchemaType.createValue(this, element, locale);
358                    addBookmark(elemPath, locale, true, elemValue);
359                    CmsXmlContentDefinition elemDef = ((CmsXmlNestedContentDefinition)elemSchemaType).getNestedContentDefinition();
360
361                    // uri
362                    Element uri = element.element(XmlNode.Uri.name());
363                    addBookmarkForElement(uri, locale, element, elemPath, elemDef);
364                    Element uriLink = uri.element(CmsXmlPage.NODE_LINK);
365                    CmsUUID elementId = null;
366                    if (uriLink == null) {
367                        // this can happen when adding the elements node to the xml content
368                        // it is not dangerous since the link has to be set before saving
369                    } else {
370                        elementId = new CmsLink(uriLink).getStructureId();
371                    }
372
373                    //                    Element createNewElement = element.element(CmsXmlContainerPage.XmlNode.CreateNew.name());
374                    //                    boolean createNew = (createNewElement != null)
375                    //                        && Boolean.parseBoolean(createNewElement.getStringValue());
376
377                    // propeties
378                    Map<String, String> propertiesMap = CmsXmlContentPropertyHelper.readProperties(
379                        this,
380                        locale,
381                        element,
382                        elemPath,
383                        elemDef);
384
385                    if (elementId != null) {
386                        elements.add(new CmsContainerElementBean(elementId, null, propertiesMap, false));
387                    }
388                }
389                m_groupContainers.put(
390                    locale,
391                    new CmsGroupContainerBean(title.getText(), description.getText(), elements, types));
392            } catch (NullPointerException e) {
393                LOG.error(
394                    org.opencms.xml.content.Messages.get().getBundle().key(
395                        org.opencms.xml.content.Messages.LOG_XMLCONTENT_INIT_BOOKMARKS_0),
396                    e);
397            }
398        }
399    }
400
401    /**
402     * Adds the given container page to the given element.<p>
403     *
404     * @param cms the current CMS object
405     * @param parent the element to add it
406     * @param groupContainer the container page to add
407     *
408     * @throws CmsException if something goes wrong
409     */
410    protected void saveGroupContainer(CmsObject cms, Element parent, CmsGroupContainerBean groupContainer)
411    throws CmsException {
412
413        parent.clearContent();
414        Element groupContainerElem = parent.addElement(XmlNode.GroupContainers.name());
415
416        groupContainerElem.addElement(XmlNode.Title.name()).addCDATA(groupContainer.getTitle());
417        groupContainerElem.addElement(XmlNode.Description.name()).addCDATA(groupContainer.getDescription());
418
419        for (String type : groupContainer.getTypes()) {
420            groupContainerElem.addElement(XmlNode.Type.name()).addCDATA(type);
421        }
422
423        // the elements
424        for (CmsContainerElementBean element : groupContainer.getElements()) {
425            CmsResource res = cms.readResource(element.getId(), CmsResourceFilter.IGNORE_EXPIRATION);
426            if (OpenCms.getResourceManager().getResourceType(res.getTypeId()).getTypeName().equals(
427                CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME)) {
428                LOG.warn(Messages.get().container(Messages.LOG_WARN_ELEMENT_GROUP_INSIDE_ELEMENT_GROUP_0));
429                continue;
430            }
431            Element elemElement = groupContainerElem.addElement(XmlNode.Element.name());
432
433            // the element
434            Element uriElem = elemElement.addElement(XmlNode.Uri.name());
435            fillResource(cms, uriElem, res);
436
437            // the properties
438            Map<String, String> properties = element.getIndividualSettings();
439            Map<String, CmsXmlContentProperty> propertiesConf = OpenCms.getADEManager().getElementSettings(cms, res);
440
441            CmsXmlContentPropertyHelper.saveProperties(cms, elemElement, properties, propertiesConf, true);
442        }
443    }
444
445    /**
446     * @see org.opencms.xml.content.CmsXmlContent#setFile(org.opencms.file.CmsFile)
447     */
448    @Override
449    protected void setFile(CmsFile file) {
450
451        // just for visibility from the factory
452        super.setFile(file);
453    }
454}