001/*
002 * File   : $Source$
003 * Date   : $Date$
004 * Version: $Revision$
005 *
006 * This library is part of OpenCms -
007 * the Open Source Content Management System
008 *
009 * Copyright (C) 2002 - 2011 Alkacon Software (http://www.alkacon.com)
010 *
011 * This library is free software; you can redistribute it and/or
012 * modify it under the terms of the GNU Lesser General Public
013 * License as published by the Free Software Foundation; either
014 * version 2.1 of the License, or (at your option) any later version.
015 *
016 * This library is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * Lesser General Public License for more details.
020 *
021 * For further information about Alkacon Software, please see the
022 * company website: http://www.alkacon.com
023 *
024 * For further information about OpenCms, please see the
025 * project website: http://www.opencms.org
026 *
027 * You should have received a copy of the GNU Lesser General Public
028 * License along with this library; if not, write to the Free Software
029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030 */
031
032package org.opencms.ade.containerpage.inherited;
033
034import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_CONFIGURATION;
035import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ELEMENT;
036import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_HIDDEN;
037import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_KEY;
038import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NAME;
039import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NEWELEMENT;
040import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ORDERKEY;
041import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_URI;
042import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_VISIBLE;
043
044import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
045import org.opencms.file.CmsFile;
046import org.opencms.file.CmsObject;
047import org.opencms.file.CmsResource;
048import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
049import org.opencms.file.types.CmsResourceTypeXmlContent;
050import org.opencms.lock.CmsLock;
051import org.opencms.main.CmsException;
052import org.opencms.main.CmsLog;
053import org.opencms.main.OpenCms;
054import org.opencms.relations.CmsRelationType;
055import org.opencms.util.CmsStringUtil;
056import org.opencms.util.CmsUUID;
057import org.opencms.xml.CmsXmlUtils;
058import org.opencms.xml.containerpage.CmsContainerElementBean;
059import org.opencms.xml.content.CmsXmlContent;
060import org.opencms.xml.content.CmsXmlContentFactory;
061import org.opencms.xml.content.CmsXmlContentProperty;
062import org.opencms.xml.content.CmsXmlContentPropertyHelper;
063import org.opencms.xml.types.CmsXmlVfsFileValue;
064import org.opencms.xml.types.I_CmsXmlContentValue;
065
066import java.util.ArrayList;
067import java.util.HashMap;
068import java.util.List;
069import java.util.Locale;
070import java.util.Map;
071import java.util.Set;
072
073import org.apache.commons.logging.Log;
074
075import org.dom4j.Element;
076
077/**
078 * A helper class for writing inherited container configuration back to a VFS file.<p>
079 */
080public class CmsContainerConfigurationWriter {
081
082    /** The logger instance for this class. */
083    @SuppressWarnings("unused")
084    private static final Log LOG = CmsLog.getLog(CmsContainerConfigurationWriter.class);
085
086    /**
087     * Saves a list of container element beans to a file in the VFS.<p>
088     *
089     * @param cms the current CMS context
090     * @param name the name of the configuration to save
091     * @param newOrdering true if a new ordering needs to be saved
092     * @param pageResource a container page or folder
093     * @param elements the elements whose data should be saved
094     *
095     * @throws CmsException if something goes wrong
096     */
097    public void save(
098        CmsObject cms,
099        String name,
100        boolean newOrdering,
101        CmsResource pageResource,
102        List<CmsContainerElementBean> elements) throws CmsException {
103
104        cms = OpenCms.initCmsObject(cms);
105        cms.getRequestContext().setSiteRoot("");
106        String configPath;
107        if (pageResource.isFolder()) {
108            configPath = CmsStringUtil.joinPaths(
109                pageResource.getRootPath(),
110                CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
111        } else {
112            configPath = CmsStringUtil.joinPaths(
113                CmsResource.getParentFolder(pageResource.getRootPath()),
114                CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
115        }
116        CmsInheritedContainerState state = OpenCms.getADEManager().getInheritedContainerState(
117            cms,
118            CmsResource.getParentFolder(CmsResource.getParentFolder(configPath)),
119            name);
120        Set<String> keys = state.getNewElementKeys();
121
122        CmsResource configRes = null;
123        boolean needToUnlock = false;
124        if (!cms.existsResource(configPath)) {
125            // create it
126            configRes = cms.createResource(
127                configPath,
128                OpenCms.getResourceManager().getResourceType(
129                    CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
130            needToUnlock = true;
131        }
132        if (configRes == null) {
133            configRes = cms.readResource(configPath);
134        }
135        CmsFile configFile = cms.readFile(configRes);
136        // make sure the internal flag is set
137        configFile.setFlags(configFile.getFlags() | CmsResource.FLAG_INTERNAL);
138        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile);
139        for (Locale localeToRemoveEntryFrom : content.getLocales()) {
140            removeExistingEntry(cms, content, localeToRemoveEntryFrom, name);
141        }
142        CmsContainerConfiguration configuration = createConfigurationBean(newOrdering, elements, keys);
143
144        Locale saveLocale = Locale.ENGLISH;
145        for (Locale locale : content.getLocales()) {
146            if (!saveLocale.equals(locale)) {
147                content.removeLocale(locale);
148            }
149        }
150        if (!content.hasLocale(saveLocale)) {
151            content.addLocale(cms, saveLocale);
152        }
153        Element parentElement = content.getLocaleNode(saveLocale);
154        serializeSingleConfiguration(cms, name, configuration, parentElement);
155        byte[] contentBytes = content.marshal();
156        configFile.setContents(contentBytes);
157        CmsLock prevLock = cms.getLock(configRes);
158        boolean alreadyLocked = prevLock.isOwnedBy(cms.getRequestContext().getCurrentUser());
159        if (!alreadyLocked) {
160            cms.lockResourceTemporary(configRes);
161            needToUnlock = true;
162        }
163        try {
164            cms.writeFile(configFile);
165        } finally {
166            if (needToUnlock) {
167                cms.unlockResource(configRes);
168            }
169        }
170    }
171
172    /**
173     * Serializes a single container configuration into an XML element.<p>
174     *
175     * @param cms the current CMS context
176     * @param name the configuration name
177     * @param config the configuration bean
178     * @param parentElement the parent element to which the new element should be attached
179     * @return the created XML element
180     *
181     * @throws CmsException if something goes wrong
182     */
183    public Element serializeSingleConfiguration(
184        CmsObject cms,
185        String name,
186        CmsContainerConfiguration config,
187        Element parentElement) throws CmsException {
188
189        List<String> visibles = new ArrayList<String>();
190        List<String> invisibles = new ArrayList<String>();
191        for (String key : config.getVisibility().keySet()) {
192            Boolean value = config.getVisibility().get(key);
193            if (value.booleanValue()) {
194                visibles.add(key);
195            } else {
196                invisibles.add(key);
197            }
198        }
199        if (config.getOrdering().isEmpty()
200            && visibles.isEmpty()
201            && invisibles.isEmpty()
202            && config.getNewElements().isEmpty()) {
203            // don't add empty inheritance configurations
204            return null;
205        }
206        Element root = parentElement.addElement(N_CONFIGURATION);
207        root.addElement(N_NAME).addCDATA(name);
208        for (String orderKey : config.getOrdering()) {
209            root.addElement(N_ORDERKEY).addCDATA(orderKey);
210        }
211        for (String visible : visibles) {
212            root.addElement(N_VISIBLE).addCDATA(visible);
213        }
214        for (String invisible : invisibles) {
215            root.addElement(N_HIDDEN).addCDATA(invisible);
216        }
217        for (Map.Entry<String, CmsContainerElementBean> entry : config.getNewElements().entrySet()) {
218            String key = entry.getKey();
219            CmsContainerElementBean elementBean = entry.getValue();
220
221            elementBean.initResource(cms);
222            Map<String, CmsXmlContentProperty> settingConfiguration = getSettingConfiguration(
223                cms,
224                elementBean.getResource());
225            CmsUUID structureId = elementBean.getId();
226            Map<String, String> settings = elementBean.getIndividualSettings();
227            Element newElementElement = root.addElement(N_NEWELEMENT);
228            newElementElement.addElement(N_KEY).addCDATA(key);
229            Element elementElement = newElementElement.addElement(N_ELEMENT);
230            Element uriElement = elementElement.addElement(N_URI);
231            CmsXmlVfsFileValue.fillEntry(uriElement, structureId, "", CmsRelationType.XML_STRONG);
232            CmsXmlContentPropertyHelper.saveProperties(cms, elementElement, settings, settingConfiguration);
233        }
234        return root;
235    }
236
237    /**
238     * Converts a list of container elements into a bean which should be saved to the inherited container configuration.<p>
239     *
240     * @param newOrdering if true, save a new ordering
241     * @param elements the elements which should be converted
242     * @param parentKeys the keys for new elements defined in the parent configurations
243     *
244     * @return the bean containing the information from the container elements which should be saved
245     */
246    protected CmsContainerConfiguration createConfigurationBean(
247        boolean newOrdering,
248        List<CmsContainerElementBean> elements,
249        Set<String> parentKeys) {
250
251        Map<String, CmsContainerElementBean> newElements = new HashMap<String, CmsContainerElementBean>();
252        List<String> ordering = new ArrayList<String>();
253        Map<String, Boolean> visibility = new HashMap<String, Boolean>();
254        for (CmsContainerElementBean elementBean : elements) {
255            CmsInheritanceInfo info = elementBean.getInheritanceInfo();
256            if (info.isNew()) {
257                newElements.put(info.getKey(), elementBean);
258            }
259        }
260        if (newOrdering) {
261            for (CmsContainerElementBean elementBean : elements) {
262                CmsInheritanceInfo info = elementBean.getInheritanceInfo();
263                // remove dangling element references
264                if (parentKeys.contains(info.getKey()) || newElements.containsKey(info.getKey())) {
265                    ordering.add(info.getKey());
266                }
267            }
268        }
269        for (CmsContainerElementBean elementBean : elements) {
270            CmsInheritanceInfo info = elementBean.getInheritanceInfo();
271            if (info.isVisible() != info.isParentVisible()) {
272                visibility.put(info.getKey(), new Boolean(info.isVisible()));
273            }
274        }
275
276        CmsContainerConfiguration configuration = new CmsContainerConfiguration(ordering, visibility, newElements);
277        return configuration;
278    }
279
280    /**
281     * Gets the setting configuration of an element.<p>
282     *
283     * @param cms the current CMS context
284     * @param resource the resource for which the setting configuration should be returned
285     * @return the setting configuration for that element
286     *
287     * @throws CmsException if something goes wrong
288     */
289    protected Map<String, CmsXmlContentProperty> getSettingConfiguration(CmsObject cms, CmsResource resource)
290    throws CmsException {
291
292        return OpenCms.getADEManager().getElementSettings(cms, resource);
293    }
294
295    /**
296     * Removes an existing inheritance container entry with a given name from the configuration file.<p>
297     *
298     * This does nothing if no such entry actually exists.<p>
299     *
300     * @param cms the current CMS context
301     * @param content the XML content
302     * @param locale the locale from which to remove the entry
303     * @param name the name of the entry
304     *
305     */
306    protected void removeExistingEntry(CmsObject cms, CmsXmlContent content, Locale locale, String name) {
307
308        if (!content.hasLocale(locale)) {
309            return;
310        }
311        String entriesXpath = N_CONFIGURATION;
312        List<I_CmsXmlContentValue> values = content.getValues(entriesXpath, locale);
313        int valueIndex = 0;
314        for (I_CmsXmlContentValue value : values) {
315            String valueXpath = value.getPath();
316            I_CmsXmlContentValue nameValue = content.getValue(CmsXmlUtils.concatXpath(valueXpath, N_NAME), locale);
317            String currentName = nameValue.getStringValue(cms);
318            if (currentName.equals(name)) {
319                content.removeValue(valueXpath, locale, valueIndex);
320                break;
321            }
322            valueIndex += 1;
323        }
324    }
325
326    /**
327     * Saves a single container configuration in an XML content object, but doesn't write it to the VFS.<p>
328     *
329     * If the XML content passed as a parameter is null, a new XML content object will be created
330     *
331     * @param cms the current CMS context
332     * @param content the XML content
333     * @param locale the locale in which the configuration should be written
334     * @param name the name of the configuration
335     * @param configuration the configuration to write
336     *
337     * @return the modified or new XML content
338     *
339     * @throws CmsException if something goes wrong
340     */
341    protected CmsXmlContent saveInContentObject(
342        CmsObject cms,
343        CmsXmlContent content,
344        Locale locale,
345        String name,
346        CmsContainerConfiguration configuration) throws CmsException {
347
348        if (content == null) {
349            content = CmsXmlContentFactory.createDocument(
350                cms,
351                locale,
352                (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType(
353                    CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
354        }
355
356        if (!content.hasLocale(locale)) {
357            content.addLocale(cms, locale);
358        }
359        Element parentElement = content.getLocaleNode(locale);
360        serializeSingleConfiguration(cms, name, configuration, parentElement);
361        return content;
362    }
363
364}