content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (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 GmbH & Co. KG, 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.file.wrapper;
  
  import org.opencms.file.CmsFile;
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsProperty;
  import org.opencms.file.CmsPropertyDefinition;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResource.CmsResourceCopyMode;
  import org.opencms.file.CmsResource.CmsResourceDeleteMode;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.file.CmsVfsResourceNotFoundException;
  import org.opencms.file.types.CmsResourceTypeFolder;
  import org.opencms.file.types.CmsResourceTypePlain;
  import org.opencms.file.types.CmsResourceTypeXmlPage;
  import org.opencms.file.types.I_CmsResourceType;
  import org.opencms.i18n.CmsEncoder;
  import org.opencms.i18n.CmsLocaleManager;
  import org.opencms.loader.CmsLoaderException;
  import org.opencms.loader.CmsResourceManager;
  import org.opencms.lock.CmsLock;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsIllegalArgumentException;
  import org.opencms.main.OpenCms;
  import org.opencms.util.CmsFileUtil;
  import org.opencms.util.CmsStringUtil;
  import org.opencms.xml.page.CmsXmlPage;
  import org.opencms.xml.page.CmsXmlPageFactory;
  
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  
  /**
   * A resource type wrapper for xml page files, which explodes the xml pages to folders.<p>
   *
   * Every resource of type "xmlpage" becomes a folder with the same name. That folder
   * contains the locales of the xml page as folders too. In the locale folder there are
   * the elements for that locale as files. The files have the names of the elements with the
   * extension "html". Additionaly there is a file in the root folder of that xml page that
   * contains the controlcode of the xml page. This file has the name "controlcode.xml".<p>
   *
   * @since 6.5.6
   */
  public class CmsResourceWrapperXmlPage extends A_CmsResourceWrapper {
  
      /** The extension to use for elements. */
      private static final String EXTENSION_ELEMENT = "html";
  
      /** The name of the element to use for the controlcode. */
      private static final String NAME_ELEMENT_CONTROLCODE = "controlcode.xml";
  
      /** Table with the states of the virtual files. */
      private static final List<String> TMP_FILE_TABLE = new ArrayList<String>();
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#addResourcesToFolder(CmsObject, String, CmsResourceFilter)
       */
      @Override
      public List<CmsResource> addResourcesToFolder(CmsObject cms, String resourcename, CmsResourceFilter filter)
      throws CmsException {
  
          CmsResource xmlPage = findXmlPage(cms, resourcename);
          if (xmlPage != null) {
              String path = getSubPath(cms, xmlPage, resourcename);
              String rootPath = cms.getRequestContext().removeSiteRoot(xmlPage.getRootPath());
  
              ArrayList<CmsResource> ret = new ArrayList<CmsResource>();
  
              CmsFile file = cms.readFile(xmlPage);
              CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
              if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
  
                  // sub path is empty -> return all existing locales for the resource
                  if (file.getLength() == 0) {
                      return ret;
                  }
  
                  List<Locale> locales = xml.getLocales();
                  Iterator<Locale> iter1 = locales.iterator();
                  while (iter1.hasNext()) {
                      Locale locale = iter1.next();
                      ret.add(getResourceForLocale(xmlPage, locale));
                  }
  
                  int plainId = OpenCms.getResourceManager().getResourceType(
                      CmsResourceTypePlain.getStaticTypeName()).getTypeId();
                  // check temp file table to add virtual file
                  Iterator<String> iter2 = getVirtualFiles().iterator();
                  while (iter2.hasNext()) {
  
                      String virtualFileName = iter2.next();
                      String virtualFilePath = rootPath + "/" + virtualFileName;
  
                      if (!TMP_FILE_TABLE.contains(virtualFilePath)) {
  
                          // read the control code resource
                          if (virtualFileName.equals(NAME_ELEMENT_CONTROLCODE)) {
  
                              CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
                              wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
                              wrap.setTypeId(plainId);
  
                              CmsFile tmpFile = wrap.getFile();
                              tmpFile.setContents(file.getContents());
                              ret.add(tmpFile);
                          }
                      }
                  }
              } else {
                  // sub path is a locale -> return all elements for this locale
                  Locale locale = CmsLocaleManager.getLocale(path);
                  List<String> names = xml.getNames(locale);
                  Iterator<String> iter = names.iterator();
                  while (iter.hasNext()) {
                      String name = iter.next();
                      String content = xml.getStringValue(cms, name, locale);
                      String fullPath = xmlPage.getRootPath() + "/" + path + "/" + name + "." + EXTENSION_ELEMENT;
                      content = prepareContent(content, cms, xmlPage, fullPath);
  
                      int length = content.length();
                      try {
                          length = content.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage)).length;
                      } catch (UnsupportedEncodingException e) {
                          // this will never happen since UTF-8 is always supported
                      }
  
                      ret.add(getResourceForElement(xmlPage, fullPath, length));
                  }
  
              }
  
              return ret;
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#copyResource(org.opencms.file.CmsObject, java.lang.String, java.lang.String, org.opencms.file.CmsResource.CmsResourceCopyMode)
       */
      @Override
      public boolean copyResource(CmsObject cms, String source, String destination, CmsResourceCopyMode siblingMode)
      throws CmsException, CmsIllegalArgumentException {
  
          // only allow copying of xml pages at whole or locales and elements inside the same xml page
          CmsResource srcXmlPage = findXmlPage(cms, source);
  
          if (srcXmlPage != null) {
              String srcPath = getSubPath(cms, srcXmlPage, source);
  
              // if the source is the xml page itself just copy the resource
              if (CmsStringUtil.isEmptyOrWhitespaceOnly(srcPath)) {
                  cms.copyResource(source, destination, siblingMode);
                  return true;
              } else {
  
                  // only a locale or an element should be copied
                  CmsResource destXmlPage = findXmlPage(cms, destination);
                  if (srcXmlPage.equals(destXmlPage)) {
  
                      // copying inside the same xml page resource
                      String destPath = getSubPath(cms, destXmlPage, destination);
  
                      String[] srcTokens = srcPath.split("/");
                      String[] destTokens = destPath.split("/");
  
                      if (srcTokens.length == destTokens.length) {
  
                          CmsFile srcFile = cms.readFile(srcXmlPage);
                          CmsXmlPage srcXml = CmsXmlPageFactory.unmarshal(cms, srcFile);
  
                          if (srcTokens.length == 1) {
  
                              if (srcTokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
  
                                  // do nothing
                              } else {
  
                                  // copy locale
                                  srcXml.copyLocale(
                                      CmsLocaleManager.getLocale(srcTokens[0]),
                                      CmsLocaleManager.getLocale(destTokens[0]));
                              }
                          } else if (srcTokens.length == 2) {
  
                              // TODO: copy element
                          }
  
                          // write files
                          srcFile.setContents(srcXml.marshal());
                          cms.writeFile(srcFile);
  
                          return true;
                      } else {
  
                          // TODO: error: destination path is invalid
                      }
                  } else {
  
                      // TODO: error: copying only allowed inside the same xml page
                  }
              }
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#createResource(org.opencms.file.CmsObject, java.lang.String, int, byte[], java.util.List)
       */
      @Override
      public CmsResource createResource(
          CmsObject cms,
          String resourcename,
          int type,
          byte[] content,
          List<CmsProperty> properties) throws CmsException, CmsIllegalArgumentException {
  
          // cut off trailing slash
          if (resourcename.endsWith("/")) {
              resourcename = resourcename.substring(0, resourcename.length() - 1);
          }
  
          // creating new xml pages if type is a folder and the name ends with .html
          if (resourcename.endsWith(".html")
              && (type == OpenCms.getResourceManager().getResourceType(
                  CmsResourceTypeFolder.getStaticTypeName()).getTypeId())) {
  
              // mark in temp file table that the visual files does not exist yet
              Iterator<String> iter = getVirtualFiles().iterator();
              while (iter.hasNext()) {
                  TMP_FILE_TABLE.add(resourcename + "/" + iter.next());
              }
  
              return cms.createResource(
                  resourcename,
                  OpenCms.getResourceManager().getResourceType(CmsResourceTypeXmlPage.getStaticTypeName()).getTypeId());
          }
  
          // find the xml page this is for
          CmsResource xmlPage = findXmlPage(cms, resourcename);
          if (xmlPage != null) {
  
              // get the path below the xml page
              String path = getSubPath(cms, xmlPage, resourcename);
  
              // and the path without the site root
              String rootPath = cms.getRequestContext().removeSiteRoot(xmlPage.getRootPath());
  
              CmsFile file = cms.readFile(xmlPage);
              CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
              // mark virtual files as created in temp file table
              if (getVirtualFiles().contains(path)) {
                  TMP_FILE_TABLE.remove(resourcename);
  
                  // at least lock file, because creating resources usually locks the resource
                  cms.lockResource(rootPath);
  
                  return file;
              }
  
              String[] tokens = path.split("/");
              if (tokens.length == 1) {
  
                  Locale locale = CmsLocaleManager.getLocale(tokens[0]);
  
                  // workaround: empty xmlpages always have the default locale "en" set
                  if (file.getLength() == 0) {
                      Iterator<Locale> iter = xml.getLocales().iterator();
                      while (iter.hasNext()) {
                          xml.removeLocale(iter.next());
                      }
                  }
  
                  // create new locale
                  xml.addLocale(cms, locale);
  
                  // save the xml page
                  file.setContents(xml.marshal());
  
                  // lock the resource
                  cms.lockResource(rootPath);
  
                  // write file
                  cms.writeFile(file);
  
              } else if (tokens.length == 2) {
  
                  String name = tokens[1];
                  if (name.endsWith(EXTENSION_ELEMENT)) {
                      name = name.substring(0, name.length() - EXTENSION_ELEMENT.length() - 1);
                  }
  
                  // create new element
                  xml.addValue(name, CmsLocaleManager.getLocale(tokens[0]));
  
                  // set the content
                  xml.setStringValue(
                      cms,
                      name,
                      CmsLocaleManager.getLocale(tokens[0]),
                      getStringValue(cms, file, content));
  
                  // save the xml page
                  file.setContents(xml.marshal());
  
                  // lock the resource
                  cms.lockResource(rootPath);
  
                  // write file
                  cms.writeFile(file);
              }
  
              return file;
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#deleteResource(CmsObject, String, org.opencms.file.CmsResource.CmsResourceDeleteMode)
       */
      @Override
      public boolean deleteResource(CmsObject cms, String resourcename, CmsResourceDeleteMode siblingMode)
      throws CmsException {
  
          // find the xml page this is for
          CmsResource xmlPage = findXmlPage(cms, resourcename);
          if (xmlPage != null) {
  
              // cut off trailing slash
              if (resourcename.endsWith("/")) {
                  resourcename = resourcename.substring(0, resourcename.length() - 1);
              }
  
              // get the path below the xml page
              String path = getSubPath(cms, xmlPage, resourcename);
  
              // if sub path is empty
              if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
  
                  // delete the xml page itself
                  cms.deleteResource(resourcename, siblingMode);
  
                  // remove all virtual files for this resource
                  Iterator<String> iter = getVirtualFiles().iterator();
                  while (iter.hasNext()) {
                      TMP_FILE_TABLE.remove(resourcename + "/" + iter.next());
                  }
  
                  return true;
              }
  
              CmsFile file = cms.readFile(xmlPage);
              CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
              String[] tokens = path.split("/");
              if (tokens.length == 1) {
  
                  // deleting a virtual file
                  if (getVirtualFiles().contains(tokens[0])) {
  
                      // mark the virtual file in the temp file table as deleted
                      TMP_FILE_TABLE.add(resourcename);
                  } else {
  
                      // delete locale
                      xml.removeLocale(CmsLocaleManager.getLocale(tokens[0]));
  
                      // save the xml page
                      file.setContents(xml.marshal());
                      cms.writeFile(file);
                  }
  
              } else if (tokens.length == 2) {
  
                  String name = tokens[1];
                  if (name.endsWith(EXTENSION_ELEMENT)) {
                      name = name.substring(0, name.length() - EXTENSION_ELEMENT.length() - 1);
                  }
  
                  // delete element
                  xml.removeValue(name, CmsLocaleManager.getLocale(tokens[0]));
  
                  // save the xml page
                  file.setContents(xml.marshal());
                  cms.writeFile(file);
              }
  
              return true;
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#getLock(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
       */
      @Override
      public CmsLock getLock(CmsObject cms, CmsResource resource) throws CmsException {
  
          CmsResource xmlPage = cms.readResource(resource.getStructureId());
          //CmsResource xmlPage = findXmlPage(cms, resource.getRootPath());
          if (xmlPage != null) {
  
              I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(xmlPage.getTypeId());
              if (resType instanceof CmsResourceTypeXmlPage) {
                  return cms.getLock(xmlPage);
              }
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.I_CmsResourceWrapper#isWrappedResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
       */
      public boolean isWrappedResource(CmsObject cms, CmsResource res) {
  
          try {
              I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(res.getTypeId());
              if (resType instanceof CmsResourceTypeXmlPage) {
                  return true;
              }
          } catch (CmsException ex) {
              // noop
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#lockResource(org.opencms.file.CmsObject, java.lang.String, boolean)
       */
      @Override
      public boolean lockResource(CmsObject cms, String resourcename, boolean temporary) throws CmsException {
  
          CmsResource res = findXmlPage(cms, resourcename);
          if (res != null) {
              String path = cms.getRequestContext().removeSiteRoot(res.getRootPath());
              if (temporary) {
                  cms.lockResourceTemporary(path);
              } else {
                  cms.lockResource(path);
              }
              return true;
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#moveResource(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
       */
      @Override
      public boolean moveResource(CmsObject cms, String source, String destination)
      throws CmsException, CmsIllegalArgumentException {
  
          // only allow copying of xml pages at whole or locales and elements inside the same xml page
          CmsResource srcXmlPage = findXmlPage(cms, source);
  
          if (srcXmlPage != null) {
              String srcPath = getSubPath(cms, srcXmlPage, source);
  
              // if the source is the xml page itself just copy the resource
              if (CmsStringUtil.isEmptyOrWhitespaceOnly(srcPath)) {
                  cms.moveResource(source, destination);
                  return true;
              } else {
  
                  // only a locale or an element should be copied
                  CmsResource destXmlPage = findXmlPage(cms, destination);
                  if (srcXmlPage.equals(destXmlPage)) {
  
                      // copying inside the same xml page resource
                      String destPath = getSubPath(cms, destXmlPage, destination);
  
                      String[] srcTokens = srcPath.split("/");
                      String[] destTokens = destPath.split("/");
  
                      if (srcTokens.length == destTokens.length) {
  
                          CmsFile srcFile = cms.readFile(srcXmlPage);
                          CmsXmlPage srcXml = CmsXmlPageFactory.unmarshal(cms, srcFile);
  
                          if (srcTokens.length == 1) {
  
                              // copy locale
                              srcXml.moveLocale(
                                  CmsLocaleManager.getLocale(srcTokens[0]),
                                  CmsLocaleManager.getLocale(destTokens[0]));
                          } else if (srcTokens.length == 2) {
  
                              // TODO: move element
                          }
  
                          // write file
                          srcFile.setContents(srcXml.marshal());
                          cms.writeFile(srcFile);
                      } else {
  
                          // TODO: error: destination path is invalid
                      }
                  } else {
  
                      // TODO: error: moving only allowed inside the same xml page
                  }
              }
  
              return true;
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readFile(CmsObject, String, CmsResourceFilter)
       */
      @Override
      public CmsFile readFile(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException {
  
          // find the xml page this is for
          CmsResource xmlPage = findXmlPage(cms, resourcename);
          if (xmlPage != null) {
  
              // cut off trailing slash
              if (resourcename.endsWith("/")) {
                  resourcename = resourcename.substring(0, resourcename.length() - 1);
              }
  
              // get the path below the xml page
              String path = getSubPath(cms, xmlPage, resourcename);
  
              String[] tokens = path.split("/");
              if (tokens.length == 1) {
  
                  CmsFile file = cms.readFile(xmlPage);
  
                  // check temp file table to remove deleted virtual files
                  if (TMP_FILE_TABLE.contains(resourcename)) {
                      return null;
                  }
  
                  // read the control code resource
                  if (tokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
  
                      CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
                      wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
  
                      CmsFile ret = wrap.getFile();
                      ret.setContents(file.getContents());
                      return ret;
                  }
              } else if (tokens.length == 2) {
  
                  CmsFile file = cms.readFile(xmlPage);
                  CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
                  // cut off the html suffix
                  String name = tokens[1];
                  if (name.endsWith("." + EXTENSION_ELEMENT)) {
                      name = name.substring(0, name.length() - 5);
                  }
  
                  if (xml.hasValue(name, CmsLocaleManager.getLocale(tokens[0]))) {
  
                      String contentString = xml.getStringValue(cms, name, CmsLocaleManager.getLocale(tokens[0]));
                      String fullPath = xmlPage.getRootPath() + "/" + tokens[0] + "/" + name + "." + EXTENSION_ELEMENT;
                      contentString = prepareContent(contentString, cms, xmlPage, fullPath);
  
                      byte[] content;
                      try {
                          content = contentString.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage));
                      } catch (UnsupportedEncodingException e) {
                          // should never happen
                          content = contentString.getBytes();
                      }
                      CmsResource resElem = getResourceForElement(xmlPage, fullPath, content.length);
                      CmsFile fileElem = new CmsFile(resElem);
  
                      fileElem.setContents(content);
                      return fileElem;
                  }
              }
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(CmsObject, String, CmsResourceFilter)
       */
      @Override
      public CmsResource readResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException {
  
          try {
  
              // try to read the resource for the resource name
              CmsResource res = null;
              try {
                  // catch this exception to try to read the resource again if it fails
                  res = cms.readResource(resourcename, filter);
              } catch (CmsException e) {
                  // read resource failed, so check if the resource name ends with a slash
                  if (resourcename.endsWith("/")) {
                      // try to read resource without a slash
                      resourcename = CmsFileUtil.removeTrailingSeparator(resourcename);
                      // try to read the resource name without ending slash
                      res = cms.readResource(resourcename, filter);
                  } else {
                      // throw the exception which caused this catch block
                      throw e;
                  }
              }
              if (CmsResourceTypeXmlPage.isXmlPage(res)) {
                  // return the xml page resource as a folder
                  return wrapResource(cms, res);
              }
  
              return null;
          } catch (CmsVfsResourceNotFoundException ex) {
  
              // find the xml page this is for
              CmsResource xmlPage = findXmlPage(cms, resourcename);
              if (xmlPage != null) {
  
                  // cut off trailing slash
                  if (resourcename.endsWith("/")) {
                      resourcename = resourcename.substring(0, resourcename.length() - 1);
                  }
  
                  // get the path below the xml page
                  String path = getSubPath(cms, xmlPage, resourcename);
  
                  CmsFile file = cms.readFile(xmlPage);
                  CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
                  String[] tokens = path.split("/");
                  if (tokens.length == 1) {
  
                      // check temp file table to remove deleted virtual files
                      if (TMP_FILE_TABLE.contains(resourcename)) {
                          return null;
                      }
  
                      // read the control code resource
                      if (tokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
  
                          CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
                          wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
                          return wrap.getResource();
                      } else {
  
                          Locale locale = CmsLocaleManager.getLocale(tokens[0]);
                          if (xml.hasLocale(locale) && (file.getLength() > 0)) {
                              return getResourceForLocale(xmlPage, locale);
                          }
                      }
                  } else if (tokens.length == 2) {
  
                      // cut off the html suffix
                      String name = tokens[1];
                      if (name.endsWith("." + EXTENSION_ELEMENT)) {
                          name = name.substring(0, name.length() - 5);
                      }
  
                      Locale locale = CmsLocaleManager.getLocale(tokens[0]);
                      if (xml.hasValue(name, locale)) {
                          String content = xml.getStringValue(cms, name, locale);
                          String fullPath = xmlPage.getRootPath()
                              + "/"
                              + tokens[0]
                              + "/"
                              + name
                              + "."
                              + EXTENSION_ELEMENT;
                          content = prepareContent(content, cms, xmlPage, fullPath);
  
                          int length = content.length();
                          try {
                              length = content.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage)).length;
                          } catch (UnsupportedEncodingException e) {
                              // this will never happen since UTF-8 is always supported
                          }
  
                          return getResourceForElement(xmlPage, fullPath, length);
                      }
                  }
  
              }
  
              return null;
          }
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#restoreLink(org.opencms.file.CmsObject, java.lang.String)
       */
      @Override
      public String restoreLink(CmsObject cms, String uri) {
  
          CmsResource res = findXmlPage(cms, uri);
          if (res != null) {
              return res.getRootPath();
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#rewriteLink(CmsObject, CmsResource)
       */
      @Override
      public String rewriteLink(CmsObject cms, CmsResource res) {
  
          if (isWrappedResource(cms, res)) {
              String path = res.getRootPath();
              if (!path.endsWith("/")) {
                  path += "/";
              }
  
              return path + NAME_ELEMENT_CONTROLCODE;
          }
  
          return null;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String)
       */
      @Override
      public boolean unlockResource(CmsObject cms, String resourcename) throws CmsException {
  
          CmsResource res = findXmlPage(cms, resourcename);
          if (res != null) {
              cms.unlockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath()));
              return true;
          }
  
          return false;
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#wrapResource(CmsObject, CmsResource)
       */
      @Override
      public CmsResource wrapResource(CmsObject cms, CmsResource res) {
  
          CmsWrappedResource wrap = new CmsWrappedResource(res);
          wrap.setFolder(true);
          return wrap.getResource();
      }
  
      /**
       * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile)
       */
      @Override
      public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException {
  
          CmsResource xmlPage = cms.readResource(resource.getStructureId());
          //CmsResource xmlPage = findXmlPage(cms, resource.getRootPath());
          if (xmlPage != null) {
  
              I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(xmlPage.getTypeId());
              if (resType instanceof CmsResourceTypeXmlPage) {
  
                  String path = getSubPath(cms, xmlPage, cms.getRequestContext().removeSiteRoot(resource.getRootPath()));
  
                  CmsFile file = cms.readFile(xmlPage);
  
                  String[] tokens = path.split("/");
                  if (tokens.length == 2) {
  
                      CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
  
                      // cut off the html suffix
                      String name = tokens[1];
                      if (name.endsWith("." + EXTENSION_ELEMENT)) {
                          name = name.substring(0, name.length() - 5);
                      }
  
                      // set content
                      String content = getStringValue(cms, file, resource.getContents());
                      content = CmsStringUtil.extractHtmlBody(content).trim();
                      xml.setStringValue(cms, name, CmsLocaleManager.getLocale(tokens[0]), content);
  
                      // write file
                      file.setContents(xml.marshal());
                      cms.writeFile(file);
  
                  }
  
                  return file;
              }
          }
  
          return null;
      }
  
      /**
       * Returns the OpenCms VFS uri of the style sheet of the resource.<p>
       *
       * @param cms the initialized CmsObject
       * @param res the resource where to read the style sheet for
       *
       * @return the OpenCms VFS uri of the style sheet of resource
       */
      protected String getUriStyleSheet(CmsObject cms, CmsResource res) {
  
          String result = "";
          try {
              String currentTemplate = getUriTemplate(cms, res);
              if (!"".equals(currentTemplate)) {
                  // read the stylesheet from the template file
                  result = cms.readPropertyObject(
                      currentTemplate,
                      CmsPropertyDefinition.PROPERTY_TEMPLATE,
                      false).getValue("");
              }
          } catch (CmsException e) {
              // noop
          }
          return result;
      }
  
      /**
       * Returns the OpenCms VFS uri of the template of the resource.<p>
       *
       * @param cms the initialized CmsObject
       * @param res the resource where to read the template for
       *
       * @return the OpenCms VFS uri of the template of the resource
       */
      protected String getUriTemplate(CmsObject cms, CmsResource res) {
  
          String result = "";
          try {
              result = cms.readPropertyObject(
                  cms.getRequestContext().removeSiteRoot(res.getRootPath()),
                  CmsPropertyDefinition.PROPERTY_TEMPLATE,
                  true).getValue("");
          } catch (CmsException e) {
              // noop
          }
          return result;
      }
  
      /**
       * Prepare the content of a xml page before returning.<p>
       *
       * Mainly adds the basic html structure and the css style sheet.<p>
       *
       * @param content the origin content of the xml page element
       * @param cms the initialized CmsObject
       * @param xmlPage the xml page resource
       * @param path the full path to set as the title in the html head
       *
       * @return the prepared content with the added html structure
       */
      protected String prepareContent(String content, CmsObject cms, CmsResource xmlPage, String path) {
  
          // cut off eventually existing html skeleton
          content = CmsStringUtil.extractHtmlBody(content);
  
          // add tags for stylesheet
          String stylesheet = getUriStyleSheet(cms, xmlPage);
  
          // content-type
          String encoding = CmsLocaleManager.getResourceEncoding(cms, xmlPage);
          String contentType = CmsResourceManager.MIMETYPE_HTML + "; charset=" + encoding;
  
          content = CmsEncoder.adjustHtmlEncoding(content, encoding);
  
          // rewrite uri
          Object obj = cms.getRequestContext().getAttribute(CmsObjectWrapper.ATTRIBUTE_NAME);
          if (obj != null) {
              CmsObjectWrapper wrapper = (CmsObjectWrapper)obj;
              stylesheet = wrapper.rewriteLink(stylesheet);
          }
  
          String base = OpenCms.getSystemInfo().getOpenCmsContext();
  
          StringBuffer result = new StringBuffer(content.length() + 1024);
          result.append("<html><head>");
  
          // meta: content-type
          result.append("<meta http-equiv=\"content-type\" content=\"");
          result.append(contentType);
          result.append("\">");
  
          // title as full path
          result.append("<title>");
          result.append(cms.getRequestContext().removeSiteRoot(path));
          result.append("</title>");
  
          // stylesheet
          if (!"".equals(stylesheet)) {
              result.append("<link href=\"");
              result.append(base);
              result.append(stylesheet);
              result.append("\" rel=\"stylesheet\" type=\"text/css\">");
          }
  
          result.append("</head><body>");
          result.append(content);
          result.append("</body></html>");
          content = result.toString();
  
          return content.trim();
      }
  
      /**
       * Returns the {@link CmsResource} of the xml page which belongs to
       * the given resource name (full path).<p>
       *
       * It works up the path till a resource for the path exists in the VFS.
       * If the found resource is a xml page, this resource is returned. If
       * the path does not belong to a xml page <code>null</code> will be returned.<p>
       *
       * @param cms the initialized CmsObject
       * @param resourcename the name of the resource (full path) to check
       *
       * @return the found resource of type xml page or null if not found
       */
      private CmsResource findXmlPage(CmsObject cms, String resourcename) {
  
          // get the full folder path of the resource to start from
          String path = cms.getRequestContext().removeSiteRoot(resourcename);
          // the path without the trailing slash
          // for example: .../xmlpage.xml/ -> .../xmlpagepage.xml
          String reducedPath = CmsFileUtil.removeTrailingSeparator(path);
          do {
  
              // check if a resource without the trailing shalsh exists
              boolean existResource = cms.existsResource(reducedPath);
              // check if the current folder exists
              if (cms.existsResource(path) || existResource) {
                  // prove if a resource without the trailing slash does exist
                  if (existResource) {
                      // a resource without the trailing slash does exist, so take the path without the trailing slash
                      path = reducedPath;
                  }
                  try {
                      CmsResource res = cms.readResource(path);
                      I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(res.getTypeId());
                      if (resType instanceof CmsResourceTypeXmlPage) {
                          return res;
                      } else {
                          break;
                      }
                  } catch (CmsException ex) {
                      break;
                  }
  
              } else {
  
                  // folder does not exist, go up one folder
                  path = CmsResource.getParentFolder(path);
                  if (path.endsWith("/")) {
                      path = path.substring(0, path.length() - 1);
                  }
              }
  
              if (CmsStringUtil.isEmpty(path)) {
  
                  // site root or root folder reached and no folder found
                  break;
              }
          } while (true);
  
          return null;
      }
  
      /**
       * Returns a virtual resource for an element inside a locale.<p>
       *
       * A new (virtual) resource is created with the given path and length. The
       * new created resource uses the values of the origin resource of the xml page
       * where it is possible.<p>
       *
       * @param xmlPage the xml page resource with the element to create a virtual resource
       * @param path the full path to set for the resource
       * @param length the length of the element content
       *
       * @return a new created virtual {@link CmsResource}
       */
      private CmsResource getResourceForElement(CmsResource xmlPage, String path, int length) {
  
          CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
          wrap.setRootPath(path);
          int plainId;
          try {
              plainId = OpenCms.getResourceManager().getResourceType(
                  CmsResourceTypePlain.getStaticTypeName()).getTypeId();
          } catch (CmsLoaderException e) {
              // this should really never happen
              plainId = CmsResourceTypePlain.getStaticTypeId();
          }
          wrap.setTypeId(plainId);
          wrap.setFolder(false);
          wrap.setLength(length);
  
          return wrap.getResource();
      }
  
      /**
       * Creates a new virtual resource for the locale in the xml page as a folder.<p>
       *
       * The new created resource uses the values of the origin resource of the xml page where it is possible.<p>
       *
       * @param xmlPage the xml page resource with the locale to create a resource of
       * @param locale the locale in the xml page to use for the new resource
       *
       * @return a new created CmsResource
       */
      private CmsResource getResourceForLocale(CmsResource xmlPage, Locale locale) {
  
          CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
          wrap.setRootPath(xmlPage.getRootPath() + "/" + locale.getLanguage() + "/");
          int plainId;
          try {
              plainId = OpenCms.getResourceManager().getResourceType(
                  CmsResourceTypePlain.getStaticTypeName()).getTypeId();
          } catch (CmsLoaderException e) {
              // this should really never happen
              plainId = CmsResourceTypePlain.getStaticTypeId();
          }
          wrap.setTypeId(plainId);
          wrap.setFolder(true);
  
          return wrap.getResource();
      }
  
      /**
       * Returns the content as a string while using the correct encoding.<p>
       *
       * @param cms the initialized CmsObject
       * @param resource the resource where the content belongs to
       * @param content the byte array which should be converted into a string
       *
       * @return the content as a string
       *
       * @throws CmsException if something goes wrong
       */
      private String getStringValue(CmsObject cms, CmsResource resource, byte[] content) throws CmsException {
  
          // get the encoding for the resource
          CmsProperty prop = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true);
          String enc = prop.getValue();
          if (enc == null) {
              enc = OpenCms.getSystemInfo().getDefaultEncoding();
          }
  
          // create a String with the right encoding
          return CmsEncoder.createString(content, enc);
      }
  
      /**
       * Returns the path inside a xml page.<p>
       *
       * The remaining path inside a xml page can be the locale and the element name
       * or the name of the control code file.<p>
       *
       * @param cms the initialized CmsObject
       * @param xmlPage the xml page where the resourcename belongs to
       * @param resourcename the full path of the resource (pointing inside the xml page)
       *
       * @return the remaining path inside the xml page without the leading slash
       */
      private String getSubPath(CmsObject cms, CmsResource xmlPage, String resourcename) {
  
          if (xmlPage != null) {
              String rootPath = cms.getRequestContext().addSiteRoot(resourcename);
              String path = rootPath.substring(xmlPage.getRootPath().length());
  
              if (path.startsWith("/")) {
                  path = path.substring(1);
              }
  
              if (path.endsWith("/")) {
                  path = path.substring(0, path.length() - 1);
              }
  
              return path;
          }
  
          return null;
      }
  
      /**
       * Returns a list with virtual file names for the xml page.<p>
       *
       * Actually that is only the name of the control code file.<p>
       *
       * @return a list containing strings with the names of the virtual files
       */
      private List<String> getVirtualFiles() {
  
          ArrayList<String> list = new ArrayList<String>();
          list.add(NAME_ELEMENT_CONTROLCODE);
  
          return list;
      }
  }