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, 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.jsp.util;
  
  import org.opencms.ade.contenteditor.CmsContentService;
  import org.opencms.file.CmsObject;
  import org.opencms.gwt.shared.CmsGwtConstants;
  import org.opencms.i18n.CmsLocaleManager;
  import org.opencms.json.JSONException;
  import org.opencms.main.CmsException;
  import org.opencms.util.CmsCollectionsGenericWrapper;
  import org.opencms.util.CmsConstantMap;
  import org.opencms.util.CmsMacroResolver;
  import org.opencms.util.CmsStringUtil;
  import org.opencms.xml.CmsXmlUtils;
  import org.opencms.xml.I_CmsXmlDocument;
  import org.opencms.xml.content.CmsXmlContent;
  import org.opencms.xml.types.I_CmsXmlContentValue;
  import org.opencms.xml.xml2json.CmsXmlContentTree;
  import org.opencms.xml.xml2json.renderer.CmsJsonRendererXmlContent;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import org.apache.commons.collections.Transformer;
  
  import org.dom4j.Element;
  import org.dom4j.Node;
  
  /**
   * Allows direct access to XML content values, with possible iteration of sub-nodes.<p>
   *
   * The implementation is optimized for performance and uses lazy initializing of the
   * requested values as much as possible.<p>
   *
   * @since 7.0.2
   *
   * @see CmsJspContentAccessBean
   * @see org.opencms.jsp.CmsJspTagContentAccess
   */
  public final class CmsJspContentAccessValueWrapper extends A_CmsJspValueWrapper {
  
      /**
       * Provides a Map with Booleans that
       * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p>
       */
      public class CmsHasValueTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              return Boolean.valueOf(
                  getContentValue().getDocument().hasValue(createPath(input), getContentValue().getLocale()));
          }
      }
  
      /**
       * Provides a Map which lets the user a nested sub value from the current value,
       * the input is assumed to be a String that represents an xpath in the XML content.<p>
       */
      public class CmsRdfaTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              if (isDirectEditEnabled(getCmsObject())) {
                  return CmsContentService.getRdfaAttributes(getContentValue(), String.valueOf(input));
              } else {
                  return "";
              }
          }
      }
  
      /**
       * Provides a Map which lets the user access nested sub value Lists directly below the current value,
       * the input is assumed to be a String that represents an xpath in the XML content.<p>
       */
      public class CmsSubValueListTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              List<I_CmsXmlContentValue> values = getContentValue().getDocument().getSubValues(
                  createPath(input),
                  getContentValue().getLocale());
              List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
              Iterator<I_CmsXmlContentValue> i = values.iterator();
              while (i.hasNext()) {
                  // must iterate values from XML content and create wrapper for each
                  I_CmsXmlContentValue value = i.next();
                  result.add(createWrapper(getCmsObject(), value, getContentValue(), value.getName()));
              }
              return result;
          }
      }
  
      /**
       * Provides a Map which lets the user access nested sub value Lists from the current value,
       * the input is assumed to be a String that represents an xpath in the XML content.<p>
       */
      public class CmsValueListTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              List<I_CmsXmlContentValue> values = getContentValue().getDocument().getValues(
                  createPath(input),
                  getContentValue().getLocale());
              List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
              Iterator<I_CmsXmlContentValue> i = values.iterator();
              while (i.hasNext()) {
                  // must iterate values from XML content and create wrapper for each
                  I_CmsXmlContentValue value = i.next();
                  result.add(createWrapper(getCmsObject(), value, getContentValue(), (String)input));
              }
              return result;
          }
      }
  
      /**
       * Provides a Map which returns a nested sub value from the current value.<p>
       *
       * The input is assumed to be a String that represents an xpath in the XML content.<p>
       */
      public class CmsValueTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              if (getContentValue() != null) {
                  I_CmsXmlContentValue value = getContentValue().getDocument().getValue(
                      createPath(input),
                      getContentValue().getLocale());
                  return createWrapper(getCmsObject(), value, getContentValue(), (String)input);
              } else {
                  return NULL_VALUE_WRAPPER;
              }
          }
      }
  
      /**
       * Provides a Map which lets the user directly access sub-nodes of the XML represented by the current value,
       * the input is assumed to be a String that represents an xpath in the XML content.<p>
       */
      public class CmsXmlValueTransformer implements Transformer {
  
          /**
           * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
           */
          @Override
          public Object transform(Object input) {
  
              Node node = getContentValue().getElement().selectSingleNode(input.toString());
              if (node != null) {
                  return node.getStringValue();
              }
              return "";
          }
      }
  
      /**
       * The null value info, used to generate RDFA and DND annotations for null values.<p>
       */
      protected static class NullValueInfo {
  
          /** The content document. */
          private I_CmsXmlDocument m_content;
  
          /** The content locale. */
          private Locale m_locale;
  
          /** The parent value. */
          private I_CmsXmlContentValue m_parentValue;
  
          /** The value path name. */
          private String m_valueName;
  
          /**
           * Constructor.<p>
           *
           * @param parentValue the parent value
           * @param valueName the value path name
           */
          protected NullValueInfo(I_CmsXmlContentValue parentValue, String valueName) {
  
              m_parentValue = parentValue;
              m_valueName = valueName;
          }
  
          /**
           * @param content the content document
           * @param valueName the value path name
           * @param locale the content locale
           */
          protected NullValueInfo(I_CmsXmlDocument content, String valueName, Locale locale) {
  
              m_content = content;
              m_valueName = valueName;
              m_locale = locale;
          }
  
          /**
           * Returns the content.<p>
           *
           * @return the content
           */
          public I_CmsXmlDocument getContent() {
  
              return m_content;
          }
  
          /**
           * Returns the locale.<p>
           *
           * @return the locale
           */
          public Locale getLocale() {
  
              return m_locale;
          }
  
          /**
           * Returns the parent value.<p>
           *
           * @return the parent value
           */
          public I_CmsXmlContentValue getParentValue() {
  
              return m_parentValue;
          }
  
          /**
           * Returns the value name.<p>
           *
           * @return the value name
           */
          public String getValueName() {
  
              return m_valueName;
          }
  
      }
  
      /** Constant for the null (non existing) value. */
      protected static final CmsJspContentAccessValueWrapper NULL_VALUE_WRAPPER = new CmsJspContentAccessValueWrapper();
  
      /** Attribute for used to cache the tree format of the XML content which is used to generate JSON. */
      public static final String TEMP_XML2JSON_TREE = "xml2json.tree";
  
      /** The wrapped XML content value. */
      private I_CmsXmlContentValue m_contentValue;
  
      /** Date series information generated from the wrapped data. */
      private CmsJspDateSeriesBean m_dateSeries;
  
      /** Calculated hash code. */
      private int m_hashCode;
  
      /** The lazy initialized Map that checks if a value is available. */
      private Map<String, Boolean> m_hasValue;
  
      /** The macro resolver used to resolve macros for this value. */
      private CmsMacroResolver m_macroResolver;
  
      /** The names of the sub elements. */
      private List<String> m_names;
  
      /** The null value info, used to generate RDFA and DND annotations for null values. */
      private NullValueInfo m_nullValueInfo;
  
      /** The current value transformed into a parameter map.*/
      private Map<String, String> m_parameters;
  
      /** The lazy initialized map of RDFA for nested sub values. */
      private Map<String, String> m_rdfa;
  
      /** The lazy initialized sub value list Map. */
      private Map<String, List<CmsJspContentAccessValueWrapper>> m_subValueList;
  
      /** The lazy initialized value Map. */
      private Map<String, CmsJspContentAccessValueWrapper> m_value;
  
      /** The lazy initialized value list Map. */
      private Map<String, List<CmsJspContentAccessValueWrapper>> m_valueList;
  
      /** The lazy initialized XML element Map. */
      private Map<String, String> m_xml;
  
      /**
       * Private constructor, used for creation of NULL constant value, use factory method to create instances.<p>
       *
       * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
       */
      private CmsJspContentAccessValueWrapper() {
  
          // cast needed to avoid compiler confusion with constructors
          this((CmsObject)null, (I_CmsXmlContentValue)null);
      }
  
      /**
       * Private constructor, use factory method to create instances.<p>
       *
       * Used to create a copy with macro resolving enabled.<p>
       *
       * @param base the wrapper base
       * @param macroResolver the macro resolver to use
       *
       * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
       */
      private CmsJspContentAccessValueWrapper(CmsJspContentAccessValueWrapper base, CmsMacroResolver macroResolver) {
  
          m_cms = base.m_cms;
          m_contentValue = base.m_contentValue;
          m_hashCode = base.m_hashCode;
          m_hasValue = base.m_hasValue;
          m_macroResolver = macroResolver;
          m_value = base.m_value;
          m_valueList = base.m_valueList;
      }
  
      /**
       * Private constructor, use factory method to create instances.<p>
       *
       * @param cms the current users OpenCms context
       * @param value the value to warp
       *
       * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
       */
      private CmsJspContentAccessValueWrapper(CmsObject cms, I_CmsXmlContentValue value) {
  
          // a null value is used for constant generation
          m_cms = cms;
          m_contentValue = value;
  
          if ((m_contentValue == null) || m_contentValue.isSimpleType()) {
              // maps must all be static
              m_hasValue = CmsConstantMap.CONSTANT_BOOLEAN_FALSE_MAP;
              m_value = CmsJspContentAccessBean.CONSTANT_NULL_VALUE_WRAPPER_MAP;
              m_valueList = CmsConstantMap.CONSTANT_EMPTY_LIST_MAP;
          }
      }
  
      /**
       * Factory method to create a new XML content value wrapper.<p>
       *
       * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p>
       *
       * @param cms the current users OpenCms context
       * @param value the value to warp
       * @param parentValue the parent value, required to set the null value info
       * @param valueName the value path name
       *
       * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code>
       */
      public static CmsJspContentAccessValueWrapper createWrapper(
          CmsObject cms,
          I_CmsXmlContentValue value,
          I_CmsXmlContentValue parentValue,
          String valueName) {
  
          if ((value != null) && (cms != null)) {
              return new CmsJspContentAccessValueWrapper(cms, value);
          }
          if ((parentValue != null) && (valueName != null) && (cms != null)) {
              CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper();
              wrapper.m_nullValueInfo = new NullValueInfo(parentValue, valueName);
              wrapper.m_cms = cms;
              return wrapper;
          }
          // if no value is available,
          return NULL_VALUE_WRAPPER;
      }
  
      /**
       * Factory method to create a new XML content value wrapper.<p>
       *
       * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p>
       *
       * @param cms the current users OpenCms context
       * @param value the value to warp
       * @param content the content document, required to set the null value info
       * @param valueName the value path name
       * @param locale the selected locale
       *
       * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code>
       */
      public static CmsJspContentAccessValueWrapper createWrapper(
          CmsObject cms,
          I_CmsXmlContentValue value,
          I_CmsXmlDocument content,
          String valueName,
          Locale locale) {
  
          if ((value != null) && (cms != null)) {
              return new CmsJspContentAccessValueWrapper(cms, value);
          }
          if ((content != null) && (valueName != null) && (locale != null) && (cms != null)) {
              CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper();
              wrapper.m_nullValueInfo = new NullValueInfo(content, valueName, locale);
              wrapper.m_cms = cms;
              return wrapper;
          }
          // if no value is available,
          return NULL_VALUE_WRAPPER;
      }
  
      /**
       * Returns if direct edit is enabled.<p>
       *
       * @param cms the current cms context
       *
       * @return <code>true</code> if direct edit is enabled
       */
      static boolean isDirectEditEnabled(CmsObject cms) {
  
          return !cms.getRequestContext().getCurrentProject().isOnlineProject()
              && (cms.getRequestContext().getAttribute(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT) == null);
      }
  
      /**
       * Returns the wrapped content value.<p>
       *
       * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>.
       *
       * @return the wrapped content value
       */
      public I_CmsXmlContentValue getContentValue() {
  
          return m_contentValue;
      }
  
      /**
       * Returns <code>true</code> in case this value actually exists in the XML content it was requested from.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:if test="${content.value['Link'].exists}" &gt;
       *         The content has a "Link" value!
       *     &lt;/c:if&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return <code>true</code> in case this value actually exists in the XML content it was requested from
       */
      @Override
      public boolean getExists() {
  
          return m_contentValue != null;
      }
  
      /**
       * Returns a lazy initialized Map that provides Booleans that
       * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p>
       *
       * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
       *
       * In case the current value is not a nested XML content value, or the XML content value does not exist,
       * the {@link CmsConstantMap#CONSTANT_BOOLEAN_FALSE_MAP} is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:if test="${content.value['Link'].hasValue['Description']}" &gt;
       *         The content has a "Description" value as sub element to the "Link" value!
       *     &lt;/c:if&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * Please note that you can also test if a sub-value exists like this:<pre>
       * &lt;c:if test="${content.value['Link'].value['Description'].exists}" &gt; ... &lt;/c:if&gt;</pre>
       *
       * @return a lazy initialized Map that provides Booleans that
       *      indicate if a sub value (xpath) for the current value is available in the XML content
       */
      public Map<String, Boolean> getHasValue() {
  
          if (m_hasValue == null) {
              m_hasValue = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasValueTransformer());
          }
          return m_hasValue;
      }
  
      /**
       * Returns the annotation that enables image drag and drop for this content value.<p>
       *
       * Use to insert the annotation attributes into a HTML tag.<p>
       *
       * Only makes sense in case this node actually contains the path to an image.<p>
       *
       * Example using EL: &lt;span ${value.Image.imageDndAttr}&gt; ... &lt;/span&gt; will result in
       * &lt;span data-imagednd="..."&gt; ... &lt;/span&gt;<p>
       *
       * @return the annotation that enables image drag and drop for this content value
       */
      public String getImageDndAttr() {
  
          String result = "";
          CmsObject cms = getCmsObject();
  
          if ((cms != null)
              && (m_contentValue != null)
              && isDirectEditEnabled(cms)
              && (m_contentValue.getDocument().getFile() != null)) {
              result = CmsJspContentAccessBean.createImageDndAttr(
                  m_contentValue.getDocument().getFile().getStructureId(),
                  m_contentValue.getPath(),
                  String.valueOf(m_contentValue.getLocale()));
          }
  
          return result;
      }
  
      /**
       * Returns the node index of the XML content value in the source XML document,
       * starting with 0.<p>
       *
       * In case the XML content value does not exist, <code>-1</code> is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     The locale of the Link node: ${content.value['Link'].locale}
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return the locale of the current XML content value
       */
      public int getIndex() {
  
          if (m_contentValue == null) {
              return -1;
          }
          return m_contentValue.getIndex();
      }
  
      /**
       * Returns <code>true</code> in case the value is empty, that is either <code>null</code> or an empty String.<p>
       *
       * In case the XML content value does not exist, <code>true</code> is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:if test="${content.value['Link'].isEmpty}" &gt;
       *         The content of the "Link" value is empty.
       *     &lt;/c:if&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return <code>true</code> in case the value is empty
       */
      @Override
      public boolean getIsEmpty() {
  
          if (m_contentValue == null) {
              // this is the case for non existing values
              return true;
          }
          if (m_contentValue.isSimpleType()) {
              // return values for simple type
              return CmsStringUtil.isEmpty(m_contentValue.getStringValue(m_cms));
          } else {
              // nested types are not empty if they have any children in the XML
              return m_contentValue.getElement().elements().size() > 0;
          }
      }
  
      /**
       * Returns <code>true</code> in case the value is empty or whitespace only,
       * that is either <code>null</code> or String that contains only whitespace chars.<p>
       *
       * In case the XML content value does not exist, <code>true</code> is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:if test="${content.value['Link'].isEmptyOrWhitespaceOnly}" &gt;
       *         The content of the "Link" value is empty or contains only whitespace chars.
       *     &lt;/c:if&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return <code>true</code> in case the value is empty or whitespace only
       */
      @Override
      public boolean getIsEmptyOrWhitespaceOnly() {
  
          if (m_contentValue == null) {
              // this is the case for non existing values
              return true;
          }
          if (m_contentValue.isSimpleType()) {
              // return values for simple type
              return CmsStringUtil.isEmptyOrWhitespaceOnly(m_contentValue.getStringValue(m_cms));
          } else {
              // nested types are not empty if they have any children in the XML
              return m_contentValue.getElement().elements().isEmpty();
          }
      }
  
      /**
       * Gets the default JSON representation of a value.
       *
       * @return the default JSON representation of the value
       */
      public CmsJspJsonWrapper getJson() throws CmsException {
  
          if (!getExists()) {
              return new CmsJspJsonWrapper(null);
          }
          CmsXmlContent content = (CmsXmlContent)m_contentValue.getDocument();
          CmsXmlContentTree tree = (CmsXmlContentTree)content.getTempDataCache().get(TEMP_XML2JSON_TREE);
          CmsXmlContentTree.Node node = null;
          if (tree == null) {
              tree = new CmsXmlContentTree(content, getLocale());
              content.getTempDataCache().put(TEMP_XML2JSON_TREE, tree);
          }
          node = tree.getNodeForValue(m_contentValue);
          if (node == null) {
              return new CmsJspJsonWrapper(null);
          }
          CmsJsonRendererXmlContent renderer = new CmsJsonRendererXmlContent(getCmsObject());
          try {
              Object jsonObj = renderer.renderNode(node);
              return new CmsJspJsonWrapper(jsonObj);
          } catch (JSONException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Returns the Locale of the current XML content value.<p>
       *
       * In case the XML content value does not exist, the OpenCms system default Locale is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     The locale of the Link node: ${content.value['Link'].locale}
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return the locale of the current XML content value
       */
      public Locale getLocale() {
  
          if (m_contentValue == null) {
              return CmsLocaleManager.getDefaultLocale();
          }
          return m_contentValue.getLocale();
      }
  
      /**
       * Returns the xml node name of the wrapped content value.<p>
       *
       * @return the xml node name
       *
       * @see org.opencms.xml.types.I_CmsXmlSchemaType#getName()
       */
      public String getName() {
  
          if (m_contentValue == null) {
              return null;
          }
          return m_contentValue.getName();
      }
  
      /**
       * Returns a list that provides the names of all nested sub values
       * directly below the current value from the XML content, including the index.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:forEach items="${content.value['Items'].names}" var="elem"&gt;
       *         &lt;c:out value="${elem}" /&gt;
       *     &lt;/c:forEach&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return a list with all available elements paths (Strings) available directly below this element
       */
      public List<String> getNames() {
  
          if ((m_names == null)) {
              m_names = new ArrayList<String>();
              if (!m_contentValue.isSimpleType()) {
                  for (I_CmsXmlContentValue value : m_contentValue.getDocument().getSubValues(getPath(), getLocale())) {
                      m_names.add(CmsXmlUtils.createXpathElement(value.getName(), value.getXmlIndex() + 1));
                  }
              }
          }
          return m_names;
      }
  
      /**
       * @see org.opencms.jsp.util.A_CmsJspValueWrapper#getObjectValue()
       */
      @Override
      public Object getObjectValue() {
  
          return m_contentValue;
      }
  
      /**
       * Returns the path to the current XML content value.<p>
       *
       * In case the XML content value does not exist, an empty String <code>""</code> is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     The path to the Link node in the XML: ${content.value['Link'].path}
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return the path to the current XML content value
       */
      public String getPath() {
  
          if (m_contentValue == null) {
              return "";
          }
          return m_contentValue.getPath();
      }
  
      /**
       * Returns a lazy initialized Map that provides the RDFA for nested sub values.<p>
       *
       * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
       *
       * @return a lazy initialized Map that provides the RDFA for nested sub values
       */
      public Map<String, String> getRdfa() {
  
          if (m_rdfa == null) {
              m_rdfa = CmsCollectionsGenericWrapper.createLazyMap(new CmsRdfaTransformer());
          }
          return m_rdfa;
      }
  
      /**
       * Returns the RDF annotation to this content value.<p>
       *
       * Use to insert the annotation attributes into a HTML tag.<p>
       * Example using EL: &lt;h1 ${value.Title.rdfaAttr}&gt;${value.Title}&lt;/h1&gt; will result in
       * &lt;h1 data-oc-id="..." data-oc-field="..."&gt;My title&lt;/h1&gt;<p>
       *
       * @return the RDFA
       */
      public String getRdfaAttr() {
  
          String result = "";
          CmsObject cms = getCmsObject();
          if (cms != null) {
              if (isDirectEditEnabled(cms)) {
                  if (m_contentValue != null) {
                      // within the offline project return the OpenCms specific entity id's and property names
                      result = CmsContentService.getRdfaAttributes(m_contentValue);
                  } else if ((m_nullValueInfo != null)) {
                      if (m_nullValueInfo.getParentValue() != null) {
                          result = CmsContentService.getRdfaAttributes(
                              m_nullValueInfo.getParentValue(),
                              m_nullValueInfo.getValueName());
                      } else if (m_nullValueInfo.getContent() != null) {
                          result = CmsContentService.getRdfaAttributes(
                              m_nullValueInfo.getContent(),
                              m_nullValueInfo.getLocale(),
                              m_nullValueInfo.getValueName());
                      }
                  }
              } else {
                  // TODO: return mapped property names etc. when online
              }
          }
          return result;
      }
  
      /**
       * Short form of {@link #getResolveMacros()}.<p>
       *
       * @return a value wrapper with macro resolving turned on
       *
       * @see #getResolveMacros()
       */
      public CmsJspContentAccessValueWrapper getResolve() {
  
          return getResolveMacros();
      }
  
      /**
       * Turn on macro resolving for the wrapped value.<p>
       *
       * Macro resolving is turned off by default.
       * When turned on, a macro resolver is initialized with
       * the current OpenCms user context and the URI of the current resource.
       * This means known macros contained in the wrapped value will be resolved when the output String is generated.
       * For example, a <code>%(property.Title)</code> in the value would be replaced with the
       * value of the title property. Macros that can not be resolved will be kept.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     The text with macros resolved: ${content.value['Text'].resolveMacros}
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return a value wrapper with macro resolving turned on
       *
       * @see CmsMacroResolver
       */
      public CmsJspContentAccessValueWrapper getResolveMacros() {
  
          if (m_macroResolver == null) {
              CmsMacroResolver macroResolver = CmsMacroResolver.newInstance();
              macroResolver.setCmsObject(m_cms);
              macroResolver.setKeepEmptyMacros(true);
              return new CmsJspContentAccessValueWrapper(this, macroResolver);
          }
          // macro resolving is already turned on
          return this;
      }
  
      /**
       * Returns a lazy initialized Map that provides the Lists of sub values directly below
       * the current value from the XML content.<p>
       *
       * The provided Map key is assumed to be a String that represents the relative xpath to the value.
       * Use this method in case you want to iterate over a List of sub values from the XML content.<p>
       *
       * In case the current value is not a nested XML content value, or the XML content value does not exist,
       * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:forEach var="desc" items="${content.value['Link'].subValueList['Description']}"&gt;
       *         ${desc}
       *     &lt;/c:forEach&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return a lazy initialized Map that provides a Lists of direct sub values of the current value from the XML content
       */
      public Map<String, List<CmsJspContentAccessValueWrapper>> getSubValueList() {
  
          if (m_subValueList == null) {
              m_subValueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsSubValueListTransformer());
          }
          return m_subValueList;
      }
  
      /**
       * Converts a date series configuration to a date series bean.
       * @return the date series bean.
       */
      public CmsJspDateSeriesBean getToDateSeries() {
  
          if (m_dateSeries == null) {
              m_dateSeries = new CmsJspDateSeriesBean(this, m_cms.getRequestContext().getLocale());
          }
          return m_dateSeries;
      }
  
      /**
       * Transforms the current value into a parameter map.<p>
       *
       * The current value must be a nested content with sub-values that
       * have exactly 2 values each, for example like this:<p>
       *
       * <pre>
       * &lt;Parameters&gt;
       *   &lt;Key&gt;&lt;foo&gt;&lt;/Key&gt;
       *   &lt;Value&gt;&lt;bar&gt;&lt;/Value&gt;
       * &lt;/Parameters&gt;
       * &lt;Parameters&gt;
       *   &lt;Key&gt;&lt;foo2&gt;&lt;/Key&gt;
       *   &lt;Value&gt;&lt;bar2&gt;&lt;/Value&gt;
       * &lt;/Parameters&gt;
       * </pre>
       *
       * Please note that the result Map is a simple String map,
       * NOT a map with {@link CmsJspContentAccessValueWrapper} classes.<p>
       *
       * @return the current value transformed into a parameter map
       */
      public Map<String, String> getToParameters() {
  
          if (m_parameters == null) {
              I_CmsXmlContentValue xmlvalue = getContentValue();
              if (xmlvalue != null) {
                  if (!getContentValue().isSimpleType()) {
                      // this is a nested content, get the list of values
                      List<I_CmsXmlContentValue> parameters = xmlvalue.getDocument().getValues(
                          xmlvalue.getPath(),
                          xmlvalue.getLocale());
                      m_parameters = new HashMap<String, String>(parameters.size());
                      for (I_CmsXmlContentValue params : parameters) {
                          // iterate all elements in this value list
                          List<Element> children = params.getElement().elements();
                          if (children.size() == 2) {
                              String key = children.get(0).getText();
                              String value = children.get(1).getText();
                              m_parameters.put(key, value);
                          }
                      }
                  }
              }
              if (m_parameters == null) {
                  m_parameters = Collections.emptyMap();
              }
          }
          return m_parameters;
      }
  
      /**
       * Returns the schema type name of the wrapped content value.<p>
       *
       * @return the type name
       *
       * @see org.opencms.xml.types.I_CmsXmlSchemaType#getTypeName()
       */
      public String getTypeName() {
  
          if (m_contentValue != null) {
              return m_contentValue.getTypeName();
          }
          return null;
      }
  
      /**
       * Returns a lazy initialized Map that provides the nested sub values
       * for the current value from the XML content.<p>
       *
       * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
       *
       * In case the current value is not a nested XML content value, or the XML content value does not exist,
       * the {@link CmsJspContentAccessBean#CONSTANT_NULL_VALUE_WRAPPER_MAP} is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     The Link Description: ${content.value['Link'].value['Description']}
       * &lt;/cms:contentload&gt;</pre>
       *
       * Please note that this example will only work if the 'Link' element is mandatory in the schema definition
       * of the XML content.<p>
       *
       * @return a lazy initialized Map that provides a sub value for the current value from the XML content
       */
      public Map<String, CmsJspContentAccessValueWrapper> getValue() {
  
          if (m_value == null) {
              m_value = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueTransformer());
          }
          return m_value;
      }
  
      /**
       * Returns a lazy initialized Map that provides the Lists of nested sub values
       * for the current value from the XML content.<p>
       *
       * The provided Map key is assumed to be a String that represents the relative xpath to the value.
       * Use this method in case you want to iterate over a List of values form the XML content.<p>
       *
       * In case the current value is not a nested XML content value, or the XML content value does not exist,
       * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p>
       *
       * Usage example on a JSP with the JSTL:<pre>
       * &lt;cms:contentload ... &gt;
       *     &lt;cms:contentaccess var="content" /&gt;
       *     &lt;c:forEach var="desc" items="${content.value['Link'].valueList['Description']}"&gt;
       *         ${desc}
       *     &lt;/c:forEach&gt;
       * &lt;/cms:contentload&gt;</pre>
       *
       * @return a lazy initialized Map that provides a Lists of sub values for the current value from the XML content
       */
      public Map<String, List<CmsJspContentAccessValueWrapper>> getValueList() {
  
          if (m_valueList == null) {
              m_valueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueListTransformer());
          }
          return m_valueList;
      }
  
      /**
       * Returns a lazy initialized Map that provides direct access to the XML element
       * for the current value from the XML content.<p>
       *
       * @return a lazy initialized Map that provides direct access to the XML element for the current value from the XML content
       */
      public Map<String, String> getXmlText() {
  
          if (m_xml == null) {
              m_xml = CmsCollectionsGenericWrapper.createLazyMap(new CmsXmlValueTransformer());
          }
          return m_xml;
      }
  
      /**
       * The hash code is created from the file structure id of the underlying XML content,
       * the selected locale and the path to the node in the XML content.
       *
       * @see java.lang.Object#hashCode()
       */
      @Override
      public int hashCode() {
  
          if (m_contentValue == null) {
              return 0;
          }
          if (m_hashCode == 0) {
              StringBuffer result = new StringBuffer(64);
              result.append(m_contentValue.getDocument().getFile().getStructureId().toString());
              result.append('/');
              result.append(m_contentValue.getLocale());
              result.append('/');
              result.append(m_contentValue.getPath());
              m_hashCode = result.toString().hashCode();
          }
          return m_hashCode;
      }
  
      /**
       * Returns the wrapped OpenCms user context.<p>
       *
       * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code>.
       *
       * @deprecated use {@link #getCmsObject()} instead
       *
       * @return the wrapped OpenCms user context
       */
      @Deprecated
      public CmsObject obtainCmsObject() {
  
          return m_cms;
      }
  
      /**
       * Returns the wrapped content value.<p>
       *
       * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>.
       *
       * Method name does not start with "get" to prevent using it in the expression language.<p>
       *
       * @return the wrapped content value
       *
       * @deprecated use {@link #getContentValue()} instead
       */
      @Deprecated
      public I_CmsXmlContentValue obtainContentValue() {
  
          return m_contentValue;
      }
  
      /**
       * @see java.lang.Object#toString()
       * @see #getToString()
       */
      @Override
      public String toString() {
  
          if (m_contentValue == null) {
              // this is the case for non existing values
              return "";
          }
          if (m_contentValue.isSimpleType()) {
              // return values for simple type
              String value = m_contentValue.getStringValue(m_cms);
              if (m_macroResolver == null) {
                  // no macro resolving
                  return value;
              } else {
                  // resolve macros first
                  return m_macroResolver.resolveMacros(value);
              }
          } else {
              // nested types should not be called this way by the user
              return "";
          }
      }
  
      /**
       * Returns the path to the XML content based on the current element path.<p>
       *
       * This is used to create xpath information for sub-elements in the transformers.<p>
       *
       * @param input the additional path that is appended to the current path
       *
       * @return the path to the XML content based on the current element path
       */
      protected String createPath(Object input) {
  
          return CmsXmlUtils.concatXpath(m_contentValue.getPath(), String.valueOf(input));
      }
  }