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