content: /*
   * File   : $Source$
   * Date   : $Date$
   * Version: $Revision$
   *
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (C) 2002 - 2011 Alkacon Software (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.ade.containerpage.inherited;
  
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_CONFIGURATION;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ELEMENT;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_HIDDEN;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_KEY;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NAME;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NEWELEMENT;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ORDERKEY;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_URI;
  import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_VISIBLE;
  
  import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
  import org.opencms.file.CmsFile;
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsResource;
  import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
  import org.opencms.file.types.CmsResourceTypeXmlContent;
  import org.opencms.lock.CmsLock;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.relations.CmsRelationType;
  import org.opencms.util.CmsStringUtil;
  import org.opencms.util.CmsUUID;
  import org.opencms.xml.CmsXmlUtils;
  import org.opencms.xml.containerpage.CmsContainerElementBean;
  import org.opencms.xml.content.CmsXmlContent;
  import org.opencms.xml.content.CmsXmlContentFactory;
  import org.opencms.xml.content.CmsXmlContentProperty;
  import org.opencms.xml.content.CmsXmlContentPropertyHelper;
  import org.opencms.xml.types.CmsXmlVfsFileValue;
  import org.opencms.xml.types.I_CmsXmlContentValue;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.commons.logging.Log;
  
  import org.dom4j.Element;
  
  /**
   * A helper class for writing inherited container configuration back to a VFS file.<p>
   */
  public class CmsContainerConfigurationWriter {
  
      /** The logger instance for this class. */
      @SuppressWarnings("unused")
      private static final Log LOG = CmsLog.getLog(CmsContainerConfigurationWriter.class);
  
      /**
       * Saves a list of container element beans to a file in the VFS.<p>
       *
       * @param cms the current CMS context
       * @param name the name of the configuration to save
       * @param newOrdering true if a new ordering needs to be saved
       * @param pageResource a container page or folder
       * @param elements the elements whose data should be saved
       *
       * @throws CmsException if something goes wrong
       */
      public void save(
          CmsObject cms,
          String name,
          boolean newOrdering,
          CmsResource pageResource,
          List<CmsContainerElementBean> elements)
      throws CmsException {
  
          cms = OpenCms.initCmsObject(cms);
          cms.getRequestContext().setSiteRoot("");
          String configPath;
          if (pageResource.isFolder()) {
              configPath = CmsStringUtil.joinPaths(
                  pageResource.getRootPath(),
                  CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
          } else {
              configPath = CmsStringUtil.joinPaths(
                  CmsResource.getParentFolder(pageResource.getRootPath()),
                  CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME);
          }
          CmsInheritedContainerState state = OpenCms.getADEManager().getInheritedContainerState(
              cms,
              CmsResource.getParentFolder(CmsResource.getParentFolder(configPath)),
              name);
          Set<String> keys = state.getNewElementKeys();
  
          CmsResource configRes = null;
          boolean needToUnlock = false;
          if (!cms.existsResource(configPath)) {
              // create it
              configRes = cms.createResource(
                  configPath,
                  OpenCms.getResourceManager().getResourceType(
                      CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
              needToUnlock = true;
          }
          if (configRes == null) {
              configRes = cms.readResource(configPath);
          }
          CmsFile configFile = cms.readFile(configRes);
          // make sure the internal flag is set
          configFile.setFlags(configFile.getFlags() | CmsResource.FLAG_INTERNAL);
          CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile);
          for (Locale localeToRemoveEntryFrom : content.getLocales()) {
              removeExistingEntry(cms, content, localeToRemoveEntryFrom, name);
          }
          CmsContainerConfiguration configuration = createConfigurationBean(newOrdering, elements, keys);
  
          Locale saveLocale = Locale.ENGLISH;
          for (Locale locale : content.getLocales()) {
              if (!saveLocale.equals(locale)) {
                  content.removeLocale(locale);
              }
          }
          if (!content.hasLocale(saveLocale)) {
              content.addLocale(cms, saveLocale);
          }
          Element parentElement = content.getLocaleNode(saveLocale);
          serializeSingleConfiguration(cms, name, configuration, parentElement);
          byte[] contentBytes = content.marshal();
          configFile.setContents(contentBytes);
          CmsLock prevLock = cms.getLock(configRes);
          boolean alreadyLocked = prevLock.isOwnedBy(cms.getRequestContext().getCurrentUser());
          if (!alreadyLocked) {
              cms.lockResourceTemporary(configRes);
              needToUnlock = true;
          }
          try {
              cms.writeFile(configFile);
          } finally {
              if (needToUnlock) {
                  cms.unlockResource(configRes);
              }
          }
      }
  
      /**
       * Serializes a single container configuration into an XML element.<p>
       *
       * @param cms the current CMS context
       * @param name the configuration name
       * @param config the configuration bean
       * @param parentElement the parent element to which the new element should be attached
       * @return the created XML element
       *
       * @throws CmsException if something goes wrong
       */
      public Element serializeSingleConfiguration(
          CmsObject cms,
          String name,
          CmsContainerConfiguration config,
          Element parentElement)
      throws CmsException {
  
          List<String> visibles = new ArrayList<String>();
          List<String> invisibles = new ArrayList<String>();
          for (String key : config.getVisibility().keySet()) {
              Boolean value = config.getVisibility().get(key);
              if (value.booleanValue()) {
                  visibles.add(key);
              } else {
                  invisibles.add(key);
              }
          }
          if (config.getOrdering().isEmpty()
              && visibles.isEmpty()
              && invisibles.isEmpty()
              && config.getNewElements().isEmpty()) {
              // don't add empty inheritance configurations
              return null;
          }
          Element root = parentElement.addElement(N_CONFIGURATION);
          root.addElement(N_NAME).addCDATA(name);
          for (String orderKey : config.getOrdering()) {
              root.addElement(N_ORDERKEY).addCDATA(orderKey);
          }
          for (String visible : visibles) {
              root.addElement(N_VISIBLE).addCDATA(visible);
          }
          for (String invisible : invisibles) {
              root.addElement(N_HIDDEN).addCDATA(invisible);
          }
          for (Map.Entry<String, CmsContainerElementBean> entry : config.getNewElements().entrySet()) {
              String key = entry.getKey();
              CmsContainerElementBean elementBean = entry.getValue();
  
              elementBean.initResource(cms);
              Map<String, CmsXmlContentProperty> settingConfiguration = getSettingConfiguration(
                  cms,
                  elementBean.getResource());
              CmsUUID structureId = elementBean.getId();
              Map<String, String> settings = elementBean.getIndividualSettings();
              Element newElementElement = root.addElement(N_NEWELEMENT);
              newElementElement.addElement(N_KEY).addCDATA(key);
              Element elementElement = newElementElement.addElement(N_ELEMENT);
              Element uriElement = elementElement.addElement(N_URI);
              CmsXmlVfsFileValue.fillEntry(uriElement, structureId, "", CmsRelationType.XML_STRONG);
              CmsXmlContentPropertyHelper.saveProperties(cms, elementElement, settings, settingConfiguration, true);
          }
          return root;
      }
  
      /**
       * Converts a list of container elements into a bean which should be saved to the inherited container configuration.<p>
       *
       * @param newOrdering if true, save a new ordering
       * @param elements the elements which should be converted
       * @param parentKeys the keys for new elements defined in the parent configurations
       *
       * @return the bean containing the information from the container elements which should be saved
       */
      protected CmsContainerConfiguration createConfigurationBean(
          boolean newOrdering,
          List<CmsContainerElementBean> elements,
          Set<String> parentKeys) {
  
          Map<String, CmsContainerElementBean> newElements = new HashMap<String, CmsContainerElementBean>();
          List<String> ordering = new ArrayList<String>();
          Map<String, Boolean> visibility = new HashMap<String, Boolean>();
          for (CmsContainerElementBean elementBean : elements) {
              CmsInheritanceInfo info = elementBean.getInheritanceInfo();
              if (info.isNew()) {
                  newElements.put(info.getKey(), elementBean);
              }
          }
          if (newOrdering) {
              for (CmsContainerElementBean elementBean : elements) {
                  CmsInheritanceInfo info = elementBean.getInheritanceInfo();
                  // remove dangling element references
                  if (parentKeys.contains(info.getKey()) || newElements.containsKey(info.getKey())) {
                      ordering.add(info.getKey());
                  }
              }
          }
          for (CmsContainerElementBean elementBean : elements) {
              CmsInheritanceInfo info = elementBean.getInheritanceInfo();
              if (info.isVisible() != info.isParentVisible()) {
                  visibility.put(info.getKey(), Boolean.valueOf(info.isVisible()));
              }
          }
  
          CmsContainerConfiguration configuration = new CmsContainerConfiguration(ordering, visibility, newElements);
          return configuration;
      }
  
      /**
       * Gets the setting configuration of an element.<p>
       *
       * @param cms the current CMS context
       * @param resource the resource for which the setting configuration should be returned
       * @return the setting configuration for that element
       *
       * @throws CmsException if something goes wrong
       */
      protected Map<String, CmsXmlContentProperty> getSettingConfiguration(CmsObject cms, CmsResource resource)
      throws CmsException {
  
          return OpenCms.getADEManager().getElementSettings(cms, resource);
      }
  
      /**
       * Removes an existing inheritance container entry with a given name from the configuration file.<p>
       *
       * This does nothing if no such entry actually exists.<p>
       *
       * @param cms the current CMS context
       * @param content the XML content
       * @param locale the locale from which to remove the entry
       * @param name the name of the entry
       *
       */
      protected void removeExistingEntry(CmsObject cms, CmsXmlContent content, Locale locale, String name) {
  
          if (!content.hasLocale(locale)) {
              return;
          }
          String entriesXpath = N_CONFIGURATION;
          List<I_CmsXmlContentValue> values = content.getValues(entriesXpath, locale);
          int valueIndex = 0;
          for (I_CmsXmlContentValue value : values) {
              String valueXpath = value.getPath();
              I_CmsXmlContentValue nameValue = content.getValue(CmsXmlUtils.concatXpath(valueXpath, N_NAME), locale);
              String currentName = nameValue.getStringValue(cms);
              if (currentName.equals(name)) {
                  content.removeValue(valueXpath, locale, valueIndex);
                  break;
              }
              valueIndex += 1;
          }
      }
  
      /**
       * Saves a single container configuration in an XML content object, but doesn't write it to the VFS.<p>
       *
       * If the XML content passed as a parameter is null, a new XML content object will be created
       *
       * @param cms the current CMS context
       * @param content the XML content
       * @param locale the locale in which the configuration should be written
       * @param name the name of the configuration
       * @param configuration the configuration to write
       *
       * @return the modified or new XML content
       *
       * @throws CmsException if something goes wrong
       */
      protected CmsXmlContent saveInContentObject(
          CmsObject cms,
          CmsXmlContent content,
          Locale locale,
          String name,
          CmsContainerConfiguration configuration)
      throws CmsException {
  
          if (content == null) {
              content = CmsXmlContentFactory.createDocument(
                  cms,
                  locale,
                  (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType(
                      CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME));
          }
  
          if (!content.hasLocale(locale)) {
              content.addLocale(cms, locale);
          }
          Element parentElement = content.getLocaleNode(locale);
          serializeSingleConfiguration(cms, name, configuration, parentElement);
          return content;
      }
  
  }
  