001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.ade.contenteditor.CmsContentService; 031import org.opencms.file.CmsObject; 032import org.opencms.gwt.shared.CmsGwtConstants; 033import org.opencms.i18n.CmsLocaleManager; 034import org.opencms.json.JSONException; 035import org.opencms.main.CmsException; 036import org.opencms.util.CmsCollectionsGenericWrapper; 037import org.opencms.util.CmsConstantMap; 038import org.opencms.util.CmsMacroResolver; 039import org.opencms.util.CmsStringUtil; 040import org.opencms.xml.CmsXmlUtils; 041import org.opencms.xml.I_CmsXmlDocument; 042import org.opencms.xml.content.CmsXmlContent; 043import org.opencms.xml.types.I_CmsXmlContentValue; 044import org.opencms.xml.xml2json.CmsXmlContentTree; 045import org.opencms.xml.xml2json.renderer.CmsJsonRendererXmlContent; 046 047import java.util.ArrayList; 048import java.util.Collections; 049import java.util.HashMap; 050import java.util.Iterator; 051import java.util.List; 052import java.util.Locale; 053import java.util.Map; 054 055import org.apache.commons.collections.Transformer; 056 057import org.dom4j.Element; 058import org.dom4j.Node; 059 060/** 061 * Allows direct access to XML content values, with possible iteration of sub-nodes.<p> 062 * 063 * The implementation is optimized for performance and uses lazy initializing of the 064 * requested values as much as possible.<p> 065 * 066 * @since 7.0.2 067 * 068 * @see CmsJspContentAccessBean 069 * @see org.opencms.jsp.CmsJspTagContentAccess 070 */ 071public final class CmsJspContentAccessValueWrapper extends A_CmsJspValueWrapper { 072 073 /** 074 * Provides a Map with Booleans that 075 * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p> 076 */ 077 public class CmsHasValueTransformer implements Transformer { 078 079 /** 080 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 081 */ 082 @Override 083 public Object transform(Object input) { 084 085 return Boolean.valueOf( 086 getContentValue().getDocument().hasValue(createPath(input), getContentValue().getLocale())); 087 } 088 } 089 090 /** 091 * Provides a Map which lets the user a nested sub value from the current value, 092 * the input is assumed to be a String that represents an xpath in the XML content.<p> 093 */ 094 public class CmsRdfaTransformer implements Transformer { 095 096 /** 097 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 098 */ 099 @Override 100 public Object transform(Object input) { 101 102 if (isDirectEditEnabled(obtainCmsObject())) { 103 return CmsContentService.getRdfaAttributes(getContentValue(), String.valueOf(input)); 104 } else { 105 return ""; 106 } 107 } 108 } 109 110 /** 111 * Provides a Map which lets the user access nested sub value Lists directly below the current value, 112 * the input is assumed to be a String that represents an xpath in the XML content.<p> 113 */ 114 public class CmsSubValueListTransformer implements Transformer { 115 116 /** 117 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 118 */ 119 @Override 120 public Object transform(Object input) { 121 122 List<I_CmsXmlContentValue> values = getContentValue().getDocument().getSubValues( 123 createPath(input), 124 getContentValue().getLocale()); 125 List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>(); 126 Iterator<I_CmsXmlContentValue> i = values.iterator(); 127 while (i.hasNext()) { 128 // must iterate values from XML content and create wrapper for each 129 I_CmsXmlContentValue value = i.next(); 130 result.add(createWrapper(obtainCmsObject(), value, getContentValue(), value.getName())); 131 } 132 return result; 133 } 134 } 135 136 /** 137 * Provides a Map which lets the user access nested sub value Lists from the current value, 138 * the input is assumed to be a String that represents an xpath in the XML content.<p> 139 */ 140 public class CmsValueListTransformer implements Transformer { 141 142 /** 143 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 144 */ 145 @Override 146 public Object transform(Object input) { 147 148 List<I_CmsXmlContentValue> values = getContentValue().getDocument().getValues( 149 createPath(input), 150 getContentValue().getLocale()); 151 List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>(); 152 Iterator<I_CmsXmlContentValue> i = values.iterator(); 153 while (i.hasNext()) { 154 // must iterate values from XML content and create wrapper for each 155 I_CmsXmlContentValue value = i.next(); 156 result.add(createWrapper(obtainCmsObject(), value, getContentValue(), (String)input)); 157 } 158 return result; 159 } 160 } 161 162 /** 163 * Provides a Map which returns a nested sub value from the current value.<p> 164 * 165 * The input is assumed to be a String that represents an xpath in the XML content.<p> 166 */ 167 public class CmsValueTransformer implements Transformer { 168 169 /** 170 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 171 */ 172 @Override 173 public Object transform(Object input) { 174 175 I_CmsXmlContentValue value = getContentValue().getDocument().getValue( 176 createPath(input), 177 getContentValue().getLocale()); 178 return createWrapper(obtainCmsObject(), value, getContentValue(), (String)input); 179 } 180 } 181 182 /** 183 * Provides a Map which lets the user directly access sub-nodes of the XML represented by the current value, 184 * the input is assumed to be a String that represents an xpath in the XML content.<p> 185 */ 186 public class CmsXmlValueTransformer implements Transformer { 187 188 /** 189 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 190 */ 191 @Override 192 public Object transform(Object input) { 193 194 Node node = getContentValue().getElement().selectSingleNode(input.toString()); 195 if (node != null) { 196 return node.getStringValue(); 197 } 198 return ""; 199 } 200 } 201 202 /** 203 * The null value info, used to generate RDFA and DND annotations for null values.<p> 204 */ 205 protected static class NullValueInfo { 206 207 /** The content document. */ 208 private I_CmsXmlDocument m_content; 209 210 /** The content locale. */ 211 private Locale m_locale; 212 213 /** The parent value. */ 214 private I_CmsXmlContentValue m_parentValue; 215 216 /** The value path name. */ 217 private String m_valueName; 218 219 /** 220 * Constructor.<p> 221 * 222 * @param parentValue the parent value 223 * @param valueName the value path name 224 */ 225 protected NullValueInfo(I_CmsXmlContentValue parentValue, String valueName) { 226 227 m_parentValue = parentValue; 228 m_valueName = valueName; 229 } 230 231 /** 232 * @param content the content document 233 * @param valueName the value path name 234 * @param locale the content locale 235 */ 236 protected NullValueInfo(I_CmsXmlDocument content, String valueName, Locale locale) { 237 238 m_content = content; 239 m_valueName = valueName; 240 m_locale = locale; 241 } 242 243 /** 244 * Returns the content.<p> 245 * 246 * @return the content 247 */ 248 public I_CmsXmlDocument getContent() { 249 250 return m_content; 251 } 252 253 /** 254 * Returns the locale.<p> 255 * 256 * @return the locale 257 */ 258 public Locale getLocale() { 259 260 return m_locale; 261 } 262 263 /** 264 * Returns the parent value.<p> 265 * 266 * @return the parent value 267 */ 268 public I_CmsXmlContentValue getParentValue() { 269 270 return m_parentValue; 271 } 272 273 /** 274 * Returns the value name.<p> 275 * 276 * @return the value name 277 */ 278 public String getValueName() { 279 280 return m_valueName; 281 } 282 283 } 284 285 /** Constant for the null (non existing) value. */ 286 protected static final CmsJspContentAccessValueWrapper NULL_VALUE_WRAPPER = new CmsJspContentAccessValueWrapper(); 287 288 /** Attribute for used to cache the tree format of the XML content which is used to generate JSON. */ 289 public static final String TEMP_XML2JSON_TREE = "xml2json.tree"; 290 291 /** The wrapped XML content value. */ 292 private I_CmsXmlContentValue m_contentValue; 293 294 /** Date series information generated from the wrapped data. */ 295 private CmsJspDateSeriesBean m_dateSeries; 296 297 /** Calculated hash code. */ 298 private int m_hashCode; 299 300 /** The lazy initialized Map that checks if a value is available. */ 301 private Map<String, Boolean> m_hasValue; 302 303 /** The macro resolver used to resolve macros for this value. */ 304 private CmsMacroResolver m_macroResolver; 305 306 /** The names of the sub elements. */ 307 private List<String> m_names; 308 309 /** The null value info, used to generate RDFA and DND annotations for null values. */ 310 private NullValueInfo m_nullValueInfo; 311 312 /** The current value transformed into a parameter map.*/ 313 private Map<String, String> m_parameters; 314 315 /** The lazy initialized map of RDFA for nested sub values. */ 316 private Map<String, String> m_rdfa; 317 318 /** The lazy initialized sub value list Map. */ 319 private Map<String, List<CmsJspContentAccessValueWrapper>> m_subValueList; 320 321 /** The lazy initialized value Map. */ 322 private Map<String, CmsJspContentAccessValueWrapper> m_value; 323 324 /** The lazy initialized value list Map. */ 325 private Map<String, List<CmsJspContentAccessValueWrapper>> m_valueList; 326 327 /** The lazy initialized XML element Map. */ 328 private Map<String, String> m_xml; 329 330 /** 331 * Private constructor, used for creation of NULL constant value, use factory method to create instances.<p> 332 * 333 * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String) 334 */ 335 private CmsJspContentAccessValueWrapper() { 336 337 // cast needed to avoid compiler confusion with constructors 338 this((CmsObject)null, (I_CmsXmlContentValue)null); 339 } 340 341 /** 342 * Private constructor, use factory method to create instances.<p> 343 * 344 * Used to create a copy with macro resolving enabled.<p> 345 * 346 * @param base the wrapper base 347 * @param macroResolver the macro resolver to use 348 * 349 * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String) 350 */ 351 private CmsJspContentAccessValueWrapper(CmsJspContentAccessValueWrapper base, CmsMacroResolver macroResolver) { 352 353 m_cms = base.m_cms; 354 m_contentValue = base.m_contentValue; 355 m_hashCode = base.m_hashCode; 356 m_hasValue = base.m_hasValue; 357 m_macroResolver = macroResolver; 358 m_value = base.m_value; 359 m_valueList = base.m_valueList; 360 } 361 362 /** 363 * Private constructor, use factory method to create instances.<p> 364 * 365 * @param cms the current users OpenCms context 366 * @param value the value to warp 367 * 368 * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String) 369 */ 370 private CmsJspContentAccessValueWrapper(CmsObject cms, I_CmsXmlContentValue value) { 371 372 // a null value is used for constant generation 373 m_cms = cms; 374 m_contentValue = value; 375 376 if ((m_contentValue == null) || m_contentValue.isSimpleType()) { 377 // maps must all be static 378 m_hasValue = CmsConstantMap.CONSTANT_BOOLEAN_FALSE_MAP; 379 m_value = CmsJspContentAccessBean.CONSTANT_NULL_VALUE_WRAPPER_MAP; 380 m_valueList = CmsConstantMap.CONSTANT_EMPTY_LIST_MAP; 381 } 382 } 383 384 /** 385 * Factory method to create a new XML content value wrapper.<p> 386 * 387 * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p> 388 * 389 * @param cms the current users OpenCms context 390 * @param value the value to warp 391 * @param parentValue the parent value, required to set the null value info 392 * @param valueName the value path name 393 * 394 * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code> 395 */ 396 public static CmsJspContentAccessValueWrapper createWrapper( 397 CmsObject cms, 398 I_CmsXmlContentValue value, 399 I_CmsXmlContentValue parentValue, 400 String valueName) { 401 402 if ((value != null) && (cms != null)) { 403 return new CmsJspContentAccessValueWrapper(cms, value); 404 } 405 if ((parentValue != null) && (valueName != null) && (cms != null)) { 406 CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper(); 407 wrapper.m_nullValueInfo = new NullValueInfo(parentValue, valueName); 408 wrapper.m_cms = cms; 409 return wrapper; 410 } 411 // if no value is available, 412 return NULL_VALUE_WRAPPER; 413 } 414 415 /** 416 * Factory method to create a new XML content value wrapper.<p> 417 * 418 * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p> 419 * 420 * @param cms the current users OpenCms context 421 * @param value the value to warp 422 * @param content the content document, required to set the null value info 423 * @param valueName the value path name 424 * @param locale the selected locale 425 * 426 * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code> 427 */ 428 public static CmsJspContentAccessValueWrapper createWrapper( 429 CmsObject cms, 430 I_CmsXmlContentValue value, 431 I_CmsXmlDocument content, 432 String valueName, 433 Locale locale) { 434 435 if ((value != null) && (cms != null)) { 436 return new CmsJspContentAccessValueWrapper(cms, value); 437 } 438 if ((content != null) && (valueName != null) && (locale != null) && (cms != null)) { 439 CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper(); 440 wrapper.m_nullValueInfo = new NullValueInfo(content, valueName, locale); 441 wrapper.m_cms = cms; 442 return wrapper; 443 } 444 // if no value is available, 445 return NULL_VALUE_WRAPPER; 446 } 447 448 /** 449 * Returns if direct edit is enabled.<p> 450 * 451 * @param cms the current cms context 452 * 453 * @return <code>true</code> if direct edit is enabled 454 */ 455 static boolean isDirectEditEnabled(CmsObject cms) { 456 457 return !cms.getRequestContext().getCurrentProject().isOnlineProject() 458 && (cms.getRequestContext().getAttribute(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT) == null); 459 } 460 461 /** 462 * Returns the wrapped content value.<p> 463 * 464 * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>. 465 * 466 * @return the wrapped content value 467 */ 468 public I_CmsXmlContentValue getContentValue() { 469 470 return m_contentValue; 471 } 472 473 /** 474 * Returns <code>true</code> in case this value actually exists in the XML content it was requested from.<p> 475 * 476 * Usage example on a JSP with the JSTL:<pre> 477 * <cms:contentload ... > 478 * <cms:contentaccess var="content" /> 479 * <c:if test="${content.value['Link'].exists}" > 480 * The content has a "Link" value! 481 * </c:if> 482 * </cms:contentload></pre> 483 * 484 * @return <code>true</code> in case this value actually exists in the XML content it was requested from 485 */ 486 @Override 487 public boolean getExists() { 488 489 return m_contentValue != null; 490 } 491 492 /** 493 * Returns a lazy initialized Map that provides Booleans that 494 * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p> 495 * 496 * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p> 497 * 498 * In case the current value is not a nested XML content value, or the XML content value does not exist, 499 * the {@link CmsConstantMap#CONSTANT_BOOLEAN_FALSE_MAP} is returned.<p> 500 * 501 * Usage example on a JSP with the JSTL:<pre> 502 * <cms:contentload ... > 503 * <cms:contentaccess var="content" /> 504 * <c:if test="${content.value['Link'].hasValue['Description']}" > 505 * The content has a "Description" value as sub element to the "Link" value! 506 * </c:if> 507 * </cms:contentload></pre> 508 * 509 * Please note that you can also test if a sub-value exists like this:<pre> 510 * <c:if test="${content.value['Link'].value['Description'].exists}" > ... </c:if></pre> 511 * 512 * @return a lazy initialized Map that provides Booleans that 513 * indicate if a sub value (xpath) for the current value is available in the XML content 514 */ 515 public Map<String, Boolean> getHasValue() { 516 517 if (m_hasValue == null) { 518 m_hasValue = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasValueTransformer()); 519 } 520 return m_hasValue; 521 } 522 523 /** 524 * Returns the annotation that enables image drag and drop for this content value.<p> 525 * 526 * Use to insert the annotation attributes into a HTML tag.<p> 527 * 528 * Only makes sense in case this node actually contains the path to an image.<p> 529 * 530 * Example using EL: <span ${value.Image.imageDndAttr}> ... </span> will result in 531 * <span data-imagednd="..."> ... </span><p> 532 * 533 * @return the annotation that enables image drag and drop for this content value 534 */ 535 public String getImageDndAttr() { 536 537 String result = ""; 538 CmsObject cms = obtainCmsObject(); 539 540 if ((cms != null) 541 && (m_contentValue != null) 542 && isDirectEditEnabled(cms) 543 && (m_contentValue.getDocument().getFile() != null)) { 544 result = CmsJspContentAccessBean.createImageDndAttr( 545 m_contentValue.getDocument().getFile().getStructureId(), 546 m_contentValue.getPath(), 547 String.valueOf(m_contentValue.getLocale())); 548 } 549 550 return result; 551 } 552 553 /** 554 * Returns the node index of the XML content value in the source XML document, 555 * starting with 0.<p> 556 * 557 * In case the XML content value does not exist, <code>-1</code> is returned.<p> 558 * 559 * Usage example on a JSP with the JSTL:<pre> 560 * <cms:contentload ... > 561 * <cms:contentaccess var="content" /> 562 * The locale of the Link node: ${content.value['Link'].locale} 563 * </cms:contentload></pre> 564 * 565 * @return the locale of the current XML content value 566 */ 567 public int getIndex() { 568 569 if (m_contentValue == null) { 570 return -1; 571 } 572 return m_contentValue.getIndex(); 573 } 574 575 /** 576 * Returns <code>true</code> in case the value is empty, that is either <code>null</code> or an empty String.<p> 577 * 578 * In case the XML content value does not exist, <code>true</code> is returned.<p> 579 * 580 * Usage example on a JSP with the JSTL:<pre> 581 * <cms:contentload ... > 582 * <cms:contentaccess var="content" /> 583 * <c:if test="${content.value['Link'].isEmpty}" > 584 * The content of the "Link" value is empty. 585 * </c:if> 586 * </cms:contentload></pre> 587 * 588 * @return <code>true</code> in case the value is empty 589 */ 590 @Override 591 public boolean getIsEmpty() { 592 593 if (m_contentValue == null) { 594 // this is the case for non existing values 595 return true; 596 } 597 if (m_contentValue.isSimpleType()) { 598 // return values for simple type 599 return CmsStringUtil.isEmpty(m_contentValue.getStringValue(m_cms)); 600 } else { 601 // nested types are not empty if they have any children in the XML 602 return m_contentValue.getElement().elements().size() > 0; 603 } 604 } 605 606 /** 607 * Returns <code>true</code> in case the value is empty or whitespace only, 608 * that is either <code>null</code> or String that contains only whitespace chars.<p> 609 * 610 * In case the XML content value does not exist, <code>true</code> is returned.<p> 611 * 612 * Usage example on a JSP with the JSTL:<pre> 613 * <cms:contentload ... > 614 * <cms:contentaccess var="content" /> 615 * <c:if test="${content.value['Link'].isEmptyOrWhitespaceOnly}" > 616 * The content of the "Link" value is empty or contains only whitespace chars. 617 * </c:if> 618 * </cms:contentload></pre> 619 * 620 * @return <code>true</code> in case the value is empty or whitespace only 621 */ 622 @Override 623 public boolean getIsEmptyOrWhitespaceOnly() { 624 625 if (m_contentValue == null) { 626 // this is the case for non existing values 627 return true; 628 } 629 if (m_contentValue.isSimpleType()) { 630 // return values for simple type 631 return CmsStringUtil.isEmptyOrWhitespaceOnly(m_contentValue.getStringValue(m_cms)); 632 } else { 633 // nested types are not empty if they have any children in the XML 634 return m_contentValue.getElement().elements().isEmpty(); 635 } 636 } 637 638 /** 639 * Gets the default JSON representation of a value. 640 * 641 * @return the default JSON representation of the value 642 */ 643 public CmsJspJsonWrapper getJson() throws CmsException { 644 645 if (!getExists()) { 646 return new CmsJspJsonWrapper(null); 647 } 648 CmsXmlContent content = (CmsXmlContent)m_contentValue.getDocument(); 649 CmsXmlContentTree tree = (CmsXmlContentTree)content.getTempDataCache().get(TEMP_XML2JSON_TREE); 650 CmsXmlContentTree.Node node = null; 651 if (tree == null) { 652 tree = new CmsXmlContentTree(content, getLocale()); 653 content.getTempDataCache().put(TEMP_XML2JSON_TREE, tree); 654 } 655 node = tree.getNodeForValue(m_contentValue); 656 if (node == null) { 657 return new CmsJspJsonWrapper(null); 658 } 659 CmsJsonRendererXmlContent renderer = new CmsJsonRendererXmlContent(getCmsObject()); 660 try { 661 Object jsonObj = renderer.renderNode(node); 662 return new CmsJspJsonWrapper(jsonObj); 663 } catch (JSONException e) { 664 throw new RuntimeException(e); 665 } 666 } 667 668 /** 669 * Returns the Locale of the current XML content value.<p> 670 * 671 * In case the XML content value does not exist, the OpenCms system default Locale is returned.<p> 672 * 673 * Usage example on a JSP with the JSTL:<pre> 674 * <cms:contentload ... > 675 * <cms:contentaccess var="content" /> 676 * The locale of the Link node: ${content.value['Link'].locale} 677 * </cms:contentload></pre> 678 * 679 * @return the locale of the current XML content value 680 */ 681 public Locale getLocale() { 682 683 if (m_contentValue == null) { 684 return CmsLocaleManager.getDefaultLocale(); 685 } 686 return m_contentValue.getLocale(); 687 } 688 689 /** 690 * Returns the xml node name of the wrapped content value.<p> 691 * 692 * @return the xml node name 693 * 694 * @see org.opencms.xml.types.I_CmsXmlSchemaType#getName() 695 */ 696 public String getName() { 697 698 if (m_contentValue == null) { 699 return null; 700 } 701 return m_contentValue.getName(); 702 } 703 704 /** 705 * Returns a list that provides the names of all nested sub values 706 * directly below the current value from the XML content, including the index.<p> 707 * 708 * Usage example on a JSP with the JSTL:<pre> 709 * <cms:contentload ... > 710 * <cms:contentaccess var="content" /> 711 * <c:forEach items="${content.value['Items'].names}" var="elem"> 712 * <c:out value="${elem}" /> 713 * </c:forEach> 714 * </cms:contentload></pre> 715 * 716 * @return a list with all available elements paths (Strings) available directly below this element 717 */ 718 public List<String> getNames() { 719 720 if ((m_names == null)) { 721 m_names = new ArrayList<String>(); 722 if (!m_contentValue.isSimpleType()) { 723 for (I_CmsXmlContentValue value : m_contentValue.getDocument().getSubValues(getPath(), getLocale())) { 724 m_names.add(CmsXmlUtils.createXpathElement(value.getName(), value.getXmlIndex() + 1)); 725 } 726 } 727 } 728 return m_names; 729 } 730 731 /** 732 * @see org.opencms.jsp.util.A_CmsJspValueWrapper#getObjectValue() 733 */ 734 @Override 735 public Object getObjectValue() { 736 737 return m_contentValue; 738 } 739 740 /** 741 * Returns the path to the current XML content value.<p> 742 * 743 * In case the XML content value does not exist, an empty String <code>""</code> is returned.<p> 744 * 745 * Usage example on a JSP with the JSTL:<pre> 746 * <cms:contentload ... > 747 * <cms:contentaccess var="content" /> 748 * The path to the Link node in the XML: ${content.value['Link'].path} 749 * </cms:contentload></pre> 750 * 751 * @return the path to the current XML content value 752 */ 753 public String getPath() { 754 755 if (m_contentValue == null) { 756 return ""; 757 } 758 return m_contentValue.getPath(); 759 } 760 761 /** 762 * Returns a lazy initialized Map that provides the RDFA for nested sub values.<p> 763 * 764 * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p> 765 * 766 * @return a lazy initialized Map that provides the RDFA for nested sub values 767 */ 768 public Map<String, String> getRdfa() { 769 770 if (m_rdfa == null) { 771 m_rdfa = CmsCollectionsGenericWrapper.createLazyMap(new CmsRdfaTransformer()); 772 } 773 return m_rdfa; 774 } 775 776 /** 777 * Returns the RDF annotation to this content value.<p> 778 * 779 * Use to insert the annotation attributes into a HTML tag.<p> 780 * Example using EL: <h1 ${value.Title.rdfaAttr}>${value.Title}</h1> will result in 781 * <h1 data-oc-id="..." data-oc-field="...">My title</h1><p> 782 * 783 * @return the RDFA 784 */ 785 public String getRdfaAttr() { 786 787 String result = ""; 788 CmsObject cms = obtainCmsObject(); 789 if (cms != null) { 790 if (isDirectEditEnabled(cms)) { 791 if (m_contentValue != null) { 792 // within the offline project return the OpenCms specific entity id's and property names 793 result = CmsContentService.getRdfaAttributes(m_contentValue); 794 } else if ((m_nullValueInfo != null)) { 795 if (m_nullValueInfo.getParentValue() != null) { 796 result = CmsContentService.getRdfaAttributes( 797 m_nullValueInfo.getParentValue(), 798 m_nullValueInfo.getValueName()); 799 } else if (m_nullValueInfo.getContent() != null) { 800 result = CmsContentService.getRdfaAttributes( 801 m_nullValueInfo.getContent(), 802 m_nullValueInfo.getLocale(), 803 m_nullValueInfo.getValueName()); 804 } 805 } 806 } else { 807 // TODO: return mapped property names etc. when online 808 } 809 } 810 return result; 811 } 812 813 /** 814 * Short form of {@link #getResolveMacros()}.<p> 815 * 816 * @return a value wrapper with macro resolving turned on 817 * 818 * @see #getResolveMacros() 819 */ 820 public CmsJspContentAccessValueWrapper getResolve() { 821 822 return getResolveMacros(); 823 } 824 825 /** 826 * Turn on macro resolving for the wrapped value.<p> 827 * 828 * Macro resolving is turned off by default. 829 * When turned on, a macro resolver is initialized with 830 * the current OpenCms user context and the URI of the current resource. 831 * This means known macros contained in the wrapped value will be resolved when the output String is generated. 832 * For example, a <code>%(property.Title)</code> in the value would be replaced with the 833 * value of the title property. Macros that can not be resolved will be kept.<p> 834 * 835 * Usage example on a JSP with the JSTL:<pre> 836 * <cms:contentload ... > 837 * <cms:contentaccess var="content" /> 838 * The text with macros resolved: ${content.value['Text'].resolveMacros} 839 * </cms:contentload></pre> 840 * 841 * @return a value wrapper with macro resolving turned on 842 * 843 * @see CmsMacroResolver 844 */ 845 public CmsJspContentAccessValueWrapper getResolveMacros() { 846 847 if (m_macroResolver == null) { 848 CmsMacroResolver macroResolver = CmsMacroResolver.newInstance(); 849 macroResolver.setCmsObject(m_cms); 850 macroResolver.setKeepEmptyMacros(true); 851 return new CmsJspContentAccessValueWrapper(this, macroResolver); 852 } 853 // macro resolving is already turned on 854 return this; 855 } 856 857 /** 858 * Returns a lazy initialized Map that provides the Lists of sub values directly below 859 * the current value from the XML content.<p> 860 * 861 * The provided Map key is assumed to be a String that represents the relative xpath to the value. 862 * Use this method in case you want to iterate over a List of sub values from the XML content.<p> 863 * 864 * In case the current value is not a nested XML content value, or the XML content value does not exist, 865 * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p> 866 * 867 * Usage example on a JSP with the JSTL:<pre> 868 * <cms:contentload ... > 869 * <cms:contentaccess var="content" /> 870 * <c:forEach var="desc" items="${content.value['Link'].subValueList['Description']}"> 871 * ${desc} 872 * </c:forEach> 873 * </cms:contentload></pre> 874 * 875 * @return a lazy initialized Map that provides a Lists of direct sub values of the current value from the XML content 876 */ 877 public Map<String, List<CmsJspContentAccessValueWrapper>> getSubValueList() { 878 879 if (m_subValueList == null) { 880 m_subValueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsSubValueListTransformer()); 881 } 882 return m_subValueList; 883 } 884 885 /** 886 * Converts a date series configuration to a date series bean. 887 * @return the date series bean. 888 */ 889 public CmsJspDateSeriesBean getToDateSeries() { 890 891 if (m_dateSeries == null) { 892 m_dateSeries = new CmsJspDateSeriesBean(this, m_cms.getRequestContext().getLocale()); 893 } 894 return m_dateSeries; 895 } 896 897 /** 898 * Transforms the current value into a parameter map.<p> 899 * 900 * The current value must be a nested content with sub-values that 901 * have exactly 2 values each, for example like this:<p> 902 * 903 * <pre> 904 * <Parameters> 905 * <Key><foo></Key> 906 * <Value><bar></Value> 907 * </Parameters> 908 * <Parameters> 909 * <Key><foo2></Key> 910 * <Value><bar2></Value> 911 * </Parameters> 912 * </pre> 913 * 914 * Please note that the result Map is a simple String map, 915 * NOT a map with {@link CmsJspContentAccessValueWrapper} classes.<p> 916 * 917 * @return the current value transformed into a parameter map 918 */ 919 public Map<String, String> getToParameters() { 920 921 if (m_parameters == null) { 922 I_CmsXmlContentValue xmlvalue = getContentValue(); 923 if (xmlvalue != null) { 924 if (!getContentValue().isSimpleType()) { 925 // this is a nested content, get the list of values 926 List<I_CmsXmlContentValue> parameters = xmlvalue.getDocument().getValues( 927 xmlvalue.getPath(), 928 xmlvalue.getLocale()); 929 m_parameters = new HashMap<String, String>(parameters.size()); 930 for (I_CmsXmlContentValue params : parameters) { 931 // iterate all elements in this value list 932 List<Element> children = params.getElement().elements(); 933 if (children.size() == 2) { 934 String key = children.get(0).getText(); 935 String value = children.get(1).getText(); 936 m_parameters.put(key, value); 937 } 938 } 939 } 940 } 941 if (m_parameters == null) { 942 m_parameters = Collections.EMPTY_MAP; 943 } 944 } 945 return m_parameters; 946 } 947 948 /** 949 * Returns the schema type name of the wrapped content value.<p> 950 * 951 * @return the type name 952 * 953 * @see org.opencms.xml.types.I_CmsXmlSchemaType#getTypeName() 954 */ 955 public String getTypeName() { 956 957 if (m_contentValue != null) { 958 return m_contentValue.getTypeName(); 959 } 960 return null; 961 } 962 963 /** 964 * Returns a lazy initialized Map that provides the nested sub values 965 * for the current value from the XML content.<p> 966 * 967 * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p> 968 * 969 * In case the current value is not a nested XML content value, or the XML content value does not exist, 970 * the {@link CmsJspContentAccessBean#CONSTANT_NULL_VALUE_WRAPPER_MAP} is returned.<p> 971 * 972 * Usage example on a JSP with the JSTL:<pre> 973 * <cms:contentload ... > 974 * <cms:contentaccess var="content" /> 975 * The Link Description: ${content.value['Link'].value['Description']} 976 * </cms:contentload></pre> 977 * 978 * Please note that this example will only work if the 'Link' element is mandatory in the schema definition 979 * of the XML content.<p> 980 * 981 * @return a lazy initialized Map that provides a sub value for the current value from the XML content 982 */ 983 public Map<String, CmsJspContentAccessValueWrapper> getValue() { 984 985 if (m_value == null) { 986 m_value = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueTransformer()); 987 } 988 return m_value; 989 } 990 991 /** 992 * Returns a lazy initialized Map that provides the Lists of nested sub values 993 * for the current value from the XML content.<p> 994 * 995 * The provided Map key is assumed to be a String that represents the relative xpath to the value. 996 * Use this method in case you want to iterate over a List of values form the XML content.<p> 997 * 998 * In case the current value is not a nested XML content value, or the XML content value does not exist, 999 * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p> 1000 * 1001 * Usage example on a JSP with the JSTL:<pre> 1002 * <cms:contentload ... > 1003 * <cms:contentaccess var="content" /> 1004 * <c:forEach var="desc" items="${content.value['Link'].valueList['Description']}"> 1005 * ${desc} 1006 * </c:forEach> 1007 * </cms:contentload></pre> 1008 * 1009 * @return a lazy initialized Map that provides a Lists of sub values for the current value from the XML content 1010 */ 1011 public Map<String, List<CmsJspContentAccessValueWrapper>> getValueList() { 1012 1013 if (m_valueList == null) { 1014 m_valueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueListTransformer()); 1015 } 1016 return m_valueList; 1017 } 1018 1019 /** 1020 * Returns a lazy initialized Map that provides direct access to the XML element 1021 * for the current value from the XML content.<p> 1022 * 1023 * @return a lazy initialized Map that provides direct access to the XML element for the current value from the XML content 1024 */ 1025 public Map<String, String> getXmlText() { 1026 1027 if (m_xml == null) { 1028 m_xml = CmsCollectionsGenericWrapper.createLazyMap(new CmsXmlValueTransformer()); 1029 } 1030 return m_xml; 1031 } 1032 1033 /** 1034 * The hash code is created from the file structure id of the underlying XML content, 1035 * the selected locale and the path to the node in the XML content. 1036 * 1037 * @see java.lang.Object#hashCode() 1038 */ 1039 @Override 1040 public int hashCode() { 1041 1042 if (m_contentValue == null) { 1043 return 0; 1044 } 1045 if (m_hashCode == 0) { 1046 StringBuffer result = new StringBuffer(64); 1047 result.append(m_contentValue.getDocument().getFile().getStructureId().toString()); 1048 result.append('/'); 1049 result.append(m_contentValue.getLocale()); 1050 result.append('/'); 1051 result.append(m_contentValue.getPath()); 1052 m_hashCode = result.toString().hashCode(); 1053 } 1054 return m_hashCode; 1055 } 1056 1057 /** 1058 * Returns the wrapped OpenCms user context.<p> 1059 * 1060 * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code>. 1061 * 1062 * @deprecated use {@link #getCmsObject()} instead 1063 * 1064 * @return the wrapped OpenCms user context 1065 */ 1066 @Deprecated 1067 public CmsObject obtainCmsObject() { 1068 1069 return m_cms; 1070 } 1071 1072 /** 1073 * Returns the wrapped content value.<p> 1074 * 1075 * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>. 1076 * 1077 * Method name does not start with "get" to prevent using it in the expression language.<p> 1078 * 1079 * @return the wrapped content value 1080 * 1081 * @deprecated use {@link #getContentValue()} instead 1082 */ 1083 @Deprecated 1084 public I_CmsXmlContentValue obtainContentValue() { 1085 1086 return m_contentValue; 1087 } 1088 1089 /** 1090 * @see java.lang.Object#toString() 1091 * @see #getToString() 1092 */ 1093 @Override 1094 public String toString() { 1095 1096 if (m_contentValue == null) { 1097 // this is the case for non existing values 1098 return ""; 1099 } 1100 if (m_contentValue.isSimpleType()) { 1101 // return values for simple type 1102 String value = m_contentValue.getStringValue(m_cms); 1103 if (m_macroResolver == null) { 1104 // no macro resolving 1105 return value; 1106 } else { 1107 // resolve macros first 1108 return m_macroResolver.resolveMacros(value); 1109 } 1110 } else { 1111 // nested types should not be called this way by the user 1112 return ""; 1113 } 1114 } 1115 1116 /** 1117 * Returns the path to the XML content based on the current element path.<p> 1118 * 1119 * This is used to create xpath information for sub-elements in the transformers.<p> 1120 * 1121 * @param input the additional path that is appended to the current path 1122 * 1123 * @return the path to the XML content based on the current element path 1124 */ 1125 protected String createPath(Object input) { 1126 1127 return CmsXmlUtils.concatXpath(m_contentValue.getPath(), String.valueOf(input)); 1128 } 1129}