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