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.xml.page;
  
  import org.opencms.file.CmsFile;
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsPropertyDefinition;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.file.types.CmsResourceTypeXmlContent;
  import org.opencms.file.types.CmsResourceTypeXmlPage;
  import org.opencms.i18n.CmsEncoder;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.xml.CmsXmlEntityResolver;
  import org.opencms.xml.CmsXmlException;
  import org.opencms.xml.CmsXmlUtils;
  import org.opencms.xml.I_CmsXmlDocument;
  import org.opencms.xml.content.CmsXmlContentFactory;
  import org.opencms.xml.types.I_CmsXmlSchemaType;
  
  import java.io.UnsupportedEncodingException;
  import java.util.Locale;
  
  import javax.servlet.ServletRequest;
  
  import org.apache.commons.logging.Log;
  
  import org.dom4j.Document;
  import org.dom4j.DocumentHelper;
  import org.dom4j.Element;
  import org.xml.sax.EntityResolver;
  
  /**
   * Provides factory methods to unmarshal (read) an XML page object.<p>
   *
   * @since 6.0.0
   */
  public final class CmsXmlPageFactory {
  
      /** The log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsXmlPageFactory.class);
  
      /**
       * No instances of this class should be created.<p>
       */
      private CmsXmlPageFactory() {
  
          // noop
      }
  
      /**
       * Creates a valid XML page document,
       * containing one empty element in the given locale.<p>
       *
       * @param locale the locale to create the XML page for
       *
       * @return a valid XML page document
       */
      public static Document createDocument(Locale locale) {
  
          Document doc = DocumentHelper.createDocument();
          Element pages = doc.addElement(CmsXmlPage.NODE_PAGES);
          pages.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
          pages.addAttribute(
              I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION,
              CmsXmlPage.XMLPAGE_XSD_SYSTEM_ID);
  
          Element page = pages.addElement(CmsXmlPage.NODE_PAGE);
          page.addAttribute(CmsXmlPage.ATTRIBUTE_LANGUAGE, locale.toString());
  
          return doc;
      }
  
      /**
       * Creates a valid XML page String representation,
       * containing one empty element in the given locale.<p>
       *
       * @param locale the locale to create the XML page for
       * @param encoding the encoding to use when creating the String from the XML
       *
       * @return a valid XML page document as a String
       */
      public static String createDocument(Locale locale, String encoding) {
  
          try {
              return CmsXmlUtils.marshal(createDocument(locale), encoding);
          } catch (CmsXmlException e) {
              // this should never happen
              LOG.error(Messages.get().getBundle().key(Messages.ERR_XML_PAGE_FACT_CREATE_DOC_0), e);
              return null;
          }
      }
  
      /**
       * Factory method to unmarshal (read) a XML page instance from a byte array
       * that contains XML data.<p>
       *
       * When unmarshalling, the encoding is read directly from the XML header.
       * The given encoding is used only when marshalling the XML again later.<p>
       *
       * @param xmlData the XML data in a byte array
       * @param encoding the encoding to use when marshalling the XML page later
       * @param resolver the XML entity resolver to use
       *
       * @return a XML page instance unmarshalled from the byte array
       *
       * @throws CmsXmlException if something goes wrong
       */
      public static CmsXmlPage unmarshal(byte[] xmlData, String encoding, EntityResolver resolver)
      throws CmsXmlException {
  
          return new CmsXmlPage(CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding);
      }
  
      /**
       * Factory method to unmarshal (read) a XML page instance from a OpenCms VFS file
       * that contains XML data.<p>
       *
       * @param cms the current cms object
       * @param file the file with the XML data to unmarshal
       *
       * @return a XML page instance unmarshalled from the provided file
       *
       * @throws CmsXmlException if something goes wrong
       */
      public static CmsXmlPage unmarshal(CmsObject cms, CmsFile file) throws CmsXmlException {
  
          return CmsXmlPageFactory.unmarshal(cms, file, true);
      }
  
      /**
       * Factory method to unmarshal (read) a XML page instance from a OpenCms VFS file
       * that contains XML data, using wither the encoding set
       * in the XML file header, or the encoding set in the VFS file property.<p>
       *
       * If you are not sure about the implications of the encoding issues,
       * use {@link #unmarshal(CmsObject, CmsFile)} instead.<p>
       *
       * @param cms the current OpenCms user context
       * @param file the file with the XML data to unmarshal
       * @param keepEncoding if true, the encoding spefified in the XML header is used,
       *    otherwise the encoding from the VFS file property is used
       *
       * @return a XML page instance unmarshalled from the provided file
       *
       * @throws CmsXmlException if something goes wrong
       */
      public static CmsXmlPage unmarshal(CmsObject cms, CmsFile file, boolean keepEncoding) throws CmsXmlException {
  
          byte[] content = file.getContents();
  
          String fileName = cms.getSitePath(file);
          boolean allowRelative = false;
          try {
              allowRelative = Boolean.valueOf(
                  cms.readPropertyObject(fileName, CmsXmlPage.PROPERTY_ALLOW_RELATIVE, false).getValue()).booleanValue();
          } catch (CmsException e) {
              // allowRelative will be false
          }
  
          String encoding = null;
          try {
              encoding = cms.readPropertyObject(
                  fileName,
                  CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING,
                  true).getValue();
          } catch (CmsException e) {
              // encoding will be null
          }
          if (encoding == null) {
              encoding = OpenCms.getSystemInfo().getDefaultEncoding();
          } else {
              encoding = CmsEncoder.lookupEncoding(encoding, null);
              if (encoding == null) {
                  throw new CmsXmlException(Messages.get().container(Messages.ERR_XML_PAGE_FACT_INVALID_ENC_1, fileName));
              }
          }
  
          CmsXmlPage newPage;
          if (content.length > 0) {
              // content is initialized
              if (keepEncoding) {
                  // use the encoding from the content
                  newPage = unmarshal(content, encoding, new CmsXmlEntityResolver(cms));
              } else {
                  // use the encoding from the file property
                  // this usually only triggered by a save operation
                  try {
                      String contentStr = new String(content, encoding);
                      newPage = unmarshal(contentStr, encoding, new CmsXmlEntityResolver(cms));
                  } catch (UnsupportedEncodingException e) {
                      // this will not happen since the encodig has already been validated
                      throw new CmsXmlException(
                          Messages.get().container(Messages.ERR_XML_PAGE_FACT_INVALID_ENC_1, fileName),
                          e);
                  }
              }
          } else {
              // content is empty
              newPage = new CmsXmlPage(cms.getRequestContext().getLocale(), encoding);
          }
  
          newPage.setFile(file);
          newPage.setAllowRelativeLinks(allowRelative);
  
          return newPage;
      }
  
      /**
       * Factory method to unmarshal (read) a XML page instance from
       * a resource, using the request attributes as cache.<p>
       *
       * @param cms the current OpenCms user context
       * @param resource the resource to unmarshal
       * @param req the current request
       *
       * @return the unmarshaled XML page, or null if the given resource was not of type {@link CmsResourceTypeXmlPage}
       *
       * @throws CmsException in something goes wrong
       */
      public static CmsXmlPage unmarshal(CmsObject cms, CmsResource resource, ServletRequest req) throws CmsException {
  
          String rootPath = resource.getRootPath();
  
          if (!CmsResourceTypeXmlPage.isXmlPage(resource)) {
              // sanity check: resource must be of type XML page
              throw new CmsXmlException(
                  Messages.get().container(Messages.ERR_XML_PAGE_FACT_NO_XMLPAGE_TYPE_1, cms.getSitePath(resource)));
          }
  
          // try to get the requested page form the current request attributes
          CmsXmlPage page = (CmsXmlPage)req.getAttribute(rootPath);
  
          if (page == null) {
              // unmarshal XML structure from the file content
              page = unmarshal(cms, cms.readFile(resource));
              // store the page that was read as request attribute for future read requests
              req.setAttribute(rootPath, page);
          }
  
          return page;
      }
  
      /**
       * Factory method to unmarshal (read) a XML document instance from
       * a filename in the VFS, using the request attributes as cache.<p>
       *
       * @param cms the current OpenCms user context
       * @param filename the filename of the resource to unmarshal
       * @param req the current request
       *
       * @return the unmarshaled XML document, or <code>null</code> if the given resource was not of type {@link I_CmsXmlDocument}
       *
       * @throws CmsException in something goes wrong
       */
      public static I_CmsXmlDocument unmarshal(CmsObject cms, String filename, ServletRequest req) throws CmsException {
  
          // add site root to filename
          String rootPath = cms.getRequestContext().addSiteRoot(filename);
  
          // try to get the requested page form the current request attributes
          I_CmsXmlDocument doc = (I_CmsXmlDocument)req.getAttribute(rootPath);
  
          if (doc != null) {
              return doc;
          }
  
          // always use "ignore expiration" filter, date validity must be checked before calling this if required
          CmsFile file = cms.readFile(filename, CmsResourceFilter.IGNORE_EXPIRATION);
  
          if (CmsResourceTypeXmlPage.isXmlPage(file)) {
              // file is of type XML page
              doc = CmsXmlPageFactory.unmarshal(cms, file);
          } else if (CmsResourceTypeXmlContent.isXmlContent(file)) {
              // file is of type XML content
              doc = CmsXmlContentFactory.unmarshal(cms, file);
          } else {
              // sanity check: file type not an A_CmsXmlDocument
              throw new CmsXmlException(Messages.get().container(Messages.ERR_XML_PAGE_FACT_NO_XML_DOCUMENT_1, file));
          }
  
          // store the page that was read as request attribute for future read requests
          req.setAttribute(rootPath, doc);
  
          return doc;
      }
  
      /**
       * Factory method to unmarshal (read) a XML page instance from a String
       * that contains XML data.<p>
       *
       * When unmarshalling, the encoding is read directly from the XML header.
       * The given encoding is used only when marshalling the XML again later.<p>
       *
       * @param xmlData the XML data in a String
       * @param encoding the encoding to use when marshalling the XML page later
       * @param resolver the XML entity resolver to use
       *
       * @return a XML page instance unmarshalled from the String
       *
       * @throws CmsXmlException if something goes wrong
       */
      public static CmsXmlPage unmarshal(String xmlData, String encoding, EntityResolver resolver)
      throws CmsXmlException {
  
          return new CmsXmlPage(CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding);
      }
  }