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)
103    throws CmsException {
104
105        cms = OpenCms.initCmsObject(cms);
106        cms.getRequestContext().setSiteRoot("");
107        String configPath;
108        if (pageResource.isFolder()) {
109            configPath = CmsStringUtil.joinPaths(
110                pageResource.getRootPath(),
111                CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
112        } else {
113            configPath = CmsStringUtil.joinPaths(
114                CmsResource.getParentFolder(pageResource.getRootPath()),
115                CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
116        }
117        CmsInheritedContainerState state = OpenCms.getADEManager().getInheritedContainerState(
118            cms,
119            CmsResource.getParentFolder(CmsResource.getParentFolder(configPath)),
120            name);
121        Set<String> keys = state.getNewElementKeys();
122
123        CmsResource configRes = null;
124        boolean needToUnlock = false;
125        if (!cms.existsResource(configPath)) {
126            // create it
127            configRes = cms.createResource(
128                configPath,
129                OpenCms.getResourceManager().getResourceType(
130                    CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
131            needToUnlock = true;
132        }
133        if (configRes == null) {
134            configRes = cms.readResource(configPath);
135        }
136        CmsFile configFile = cms.readFile(configRes);
137        // make sure the internal flag is set
138        configFile.setFlags(configFile.getFlags() | CmsResource.FLAG_INTERNAL);
139        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile);
140        for (Locale localeToRemoveEntryFrom : content.getLocales()) {
141            removeExistingEntry(cms, content, localeToRemoveEntryFrom, name);
142        }
143        CmsContainerConfiguration configuration = createConfigurationBean(newOrdering, elements, keys);
144
145        Locale saveLocale = Locale.ENGLISH;
146        for (Locale locale : content.getLocales()) {
147            if (!saveLocale.equals(locale)) {
148                content.removeLocale(locale);
149            }
150        }
151        if (!content.hasLocale(saveLocale)) {
152            content.addLocale(cms, saveLocale);
153        }
154        Element parentElement = content.getLocaleNode(saveLocale);
155        serializeSingleConfiguration(cms, name, configuration, parentElement);
156        byte[] contentBytes = content.marshal();
157        configFile.setContents(contentBytes);
158        CmsLock prevLock = cms.getLock(configRes);
159        boolean alreadyLocked = prevLock.isOwnedBy(cms.getRequestContext().getCurrentUser());
160        if (!alreadyLocked) {
161            cms.lockResourceTemporary(configRes);
162            needToUnlock = true;
163        }
164        try {
165            cms.writeFile(configFile);
166        } finally {
167            if (needToUnlock) {
168                cms.unlockResource(configRes);
169            }
170        }
171    }
172
173    /**
174     * Serializes a single container configuration into an XML element.<p>
175     *
176     * @param cms the current CMS context
177     * @param name the configuration name
178     * @param config the configuration bean
179     * @param parentElement the parent element to which the new element should be attached
180     * @return the created XML element
181     *
182     * @throws CmsException if something goes wrong
183     */
184    public Element serializeSingleConfiguration(
185        CmsObject cms,
186        String name,
187        CmsContainerConfiguration config,
188        Element parentElement)
189    throws CmsException {
190
191        List<String> visibles = new ArrayList<String>();
192        List<String> invisibles = new ArrayList<String>();
193        for (String key : config.getVisibility().keySet()) {
194            Boolean value = config.getVisibility().get(key);
195            if (value.booleanValue()) {
196                visibles.add(key);
197            } else {
198                invisibles.add(key);
199            }
200        }
201        if (config.getOrdering().isEmpty()
202            && visibles.isEmpty()
203            && invisibles.isEmpty()
204            && config.getNewElements().isEmpty()) {
205            // don't add empty inheritance configurations
206            return null;
207        }
208        Element root = parentElement.addElement(N_CONFIGURATION);
209        root.addElement(N_NAME).addCDATA(name);
210        for (String orderKey : config.getOrdering()) {
211            root.addElement(N_ORDERKEY).addCDATA(orderKey);
212        }
213        for (String visible : visibles) {
214            root.addElement(N_VISIBLE).addCDATA(visible);
215        }
216        for (String invisible : invisibles) {
217            root.addElement(N_HIDDEN).addCDATA(invisible);
218        }
219        for (Map.Entry<String, CmsContainerElementBean> entry : config.getNewElements().entrySet()) {
220            String key = entry.getKey();
221            CmsContainerElementBean elementBean = entry.getValue();
222
223            elementBean.initResource(cms);
224            Map<String, CmsXmlContentProperty> settingConfiguration = getSettingConfiguration(
225                cms,
226                elementBean.getResource());
227            CmsUUID structureId = elementBean.getId();
228            Map<String, String> settings = elementBean.getIndividualSettings();
229            Element newElementElement = root.addElement(N_NEWELEMENT);
230            newElementElement.addElement(N_KEY).addCDATA(key);
231            Element elementElement = newElementElement.addElement(N_ELEMENT);
232            Element uriElement = elementElement.addElement(N_URI);
233            CmsXmlVfsFileValue.fillEntry(uriElement, structureId, "", CmsRelationType.XML_STRONG);
234            CmsXmlContentPropertyHelper.saveProperties(cms, elementElement, settings, settingConfiguration, true);
235        }
236        return root;
237    }
238
239    /**
240     * Converts a list of container elements into a bean which should be saved to the inherited container configuration.<p>
241     *
242     * @param newOrdering if true, save a new ordering
243     * @param elements the elements which should be converted
244     * @param parentKeys the keys for new elements defined in the parent configurations
245     *
246     * @return the bean containing the information from the container elements which should be saved
247     */
248    protected CmsContainerConfiguration createConfigurationBean(
249        boolean newOrdering,
250        List<CmsContainerElementBean> elements,
251        Set<String> parentKeys) {
252
253        Map<String, CmsContainerElementBean> newElements = new HashMap<String, CmsContainerElementBean>();
254        List<String> ordering = new ArrayList<String>();
255        Map<String, Boolean> visibility = new HashMap<String, Boolean>();
256        for (CmsContainerElementBean elementBean : elements) {
257            CmsInheritanceInfo info = elementBean.getInheritanceInfo();
258            if (info.isNew()) {
259                newElements.put(info.getKey(), elementBean);
260            }
261        }
262        if (newOrdering) {
263            for (CmsContainerElementBean elementBean : elements) {
264                CmsInheritanceInfo info = elementBean.getInheritanceInfo();
265                // remove dangling element references
266                if (parentKeys.contains(info.getKey()) || newElements.containsKey(info.getKey())) {
267                    ordering.add(info.getKey());
268                }
269            }
270        }
271        for (CmsContainerElementBean elementBean : elements) {
272            CmsInheritanceInfo info = elementBean.getInheritanceInfo();
273            if (info.isVisible() != info.isParentVisible()) {
274                visibility.put(info.getKey(), Boolean.valueOf(info.isVisible()));
275            }
276        }
277
278        CmsContainerConfiguration configuration = new CmsContainerConfiguration(ordering, visibility, newElements);
279        return configuration;
280    }
281
282    /**
283     * Gets the setting configuration of an element.<p>
284     *
285     * @param cms the current CMS context
286     * @param resource the resource for which the setting configuration should be returned
287     * @return the setting configuration for that element
288     *
289     * @throws CmsException if something goes wrong
290     */
291    protected Map<String, CmsXmlContentProperty> getSettingConfiguration(CmsObject cms, CmsResource resource)
292    throws CmsException {
293
294        return OpenCms.getADEManager().getElementSettings(cms, resource);
295    }
296
297    /**
298     * Removes an existing inheritance container entry with a given name from the configuration file.<p>
299     *
300     * This does nothing if no such entry actually exists.<p>
301     *
302     * @param cms the current CMS context
303     * @param content the XML content
304     * @param locale the locale from which to remove the entry
305     * @param name the name of the entry
306     *
307     */
308    protected void removeExistingEntry(CmsObject cms, CmsXmlContent content, Locale locale, String name) {
309
310        if (!content.hasLocale(locale)) {
311            return;
312        }
313        String entriesXpath = N_CONFIGURATION;
314        List<I_CmsXmlContentValue> values = content.getValues(entriesXpath, locale);
315        int valueIndex = 0;
316        for (I_CmsXmlContentValue value : values) {
317            String valueXpath = value.getPath();
318            I_CmsXmlContentValue nameValue = content.getValue(CmsXmlUtils.concatXpath(valueXpath, N_NAME), locale);
319            String currentName = nameValue.getStringValue(cms);
320            if (currentName.equals(name)) {
321                content.removeValue(valueXpath, locale, valueIndex);
322                break;
323            }
324            valueIndex += 1;
325        }
326    }
327
328    /**
329     * Saves a single container configuration in an XML content object, but doesn't write it to the VFS.<p>
330     *
331     * If the XML content passed as a parameter is null, a new XML content object will be created
332     *
333     * @param cms the current CMS context
334     * @param content the XML content
335     * @param locale the locale in which the configuration should be written
336     * @param name the name of the configuration
337     * @param configuration the configuration to write
338     *
339     * @return the modified or new XML content
340     *
341     * @throws CmsException if something goes wrong
342     */
343    protected CmsXmlContent saveInContentObject(
344        CmsObject cms,
345        CmsXmlContent content,
346        Locale locale,
347        String name,
348        CmsContainerConfiguration configuration)
349    throws CmsException {
350
351        if (content == null) {
352            content = CmsXmlContentFactory.createDocument(
353                cms,
354                locale,
355                (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType(
356                    CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
357        }
358
359        if (!content.hasLocale(locale)) {
360            content.addLocale(cms, locale);
361        }
362        Element parentElement = content.getLocaleNode(locale);
363        serializeSingleConfiguration(cms, name, configuration, parentElement);
364        return content;
365    }
366
367}