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 GmbH & Co. KG, 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; 029 030import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry; 031import org.opencms.file.CmsProperty; 032import org.opencms.file.CmsPropertyDefinition; 033import org.opencms.file.CmsResource; 034import org.opencms.i18n.CmsMessages; 035import org.opencms.jsp.CmsJspNavBuilder.NavContext; 036import org.opencms.main.CmsLog; 037import org.opencms.util.CmsCollectionsGenericWrapper; 038 039import java.util.Collections; 040import java.util.List; 041import java.util.Locale; 042import java.util.Map; 043 044import org.apache.commons.logging.Log; 045 046/** 047 * Bean to collect navigation information from a resource in the OpenCms VFS.<p> 048 * 049 * Each navigation element contains a number of information about a VFS resource, 050 * obtained either from the resources properties or attributes. 051 * You can use this information to generate a HTML navigation for 052 * files in the VFS in your template.<p> 053 * 054 * Note: this class has a natural ordering that is inconsistent with equals.<p> 055 * 056 * @since 6.0.0 057 * 058 * @see org.opencms.jsp.CmsJspNavBuilder 059 */ 060public class CmsJspNavElement implements Comparable<CmsJspNavElement> { 061 062 /** The log instance for this class. */ 063 private static final Log LOG = CmsLog.getLog(CmsJspNavElement.class); 064 065 /** The locale for which the property should be read. */ 066 protected Locale m_locale; 067 068 /** The navigation context. */ 069 protected CmsJspNavBuilder.NavContext m_navContext; 070 071 /** The navigation position has changed flag. */ 072 private boolean m_changedNavPos; 073 074 /** The file name. */ 075 private String m_fileName; 076 077 /** The has navigation flag. */ 078 private Boolean m_hasNav; 079 080 /** Flag indicating whether this is a hidden navigation entry. */ 081 private Boolean m_isHiddenNavigationEntry; 082 083 /** The properties accessed according to the chosen locale. */ 084 private Map<String, String> m_localeProperties; 085 086 /** The navigation tree level. */ 087 private int m_navTreeLevel = Integer.MIN_VALUE; 088 089 /** The navigation position. */ 090 private float m_position; 091 092 /** The properties. */ 093 private Map<String, String> m_properties; 094 095 /** The resource. */ 096 private CmsResource m_resource; 097 098 /** The site path. */ 099 private String m_sitePath; 100 101 /** The navigation text. */ 102 private String m_text; 103 104 private List<CmsJspNavElement> m_subNavigation; 105 106 /** 107 * Empty constructor required for every JavaBean, does nothing.<p> 108 * 109 * Call one of the init methods after you have created an instance 110 * of the bean. Instead of using the constructor you should use 111 * the static factory methods provided by this class to create 112 * navigation beans that are properly initialized with current 113 * OpenCms context.<p> 114 * 115 * @see CmsJspNavBuilder#getNavigationForResource() 116 * @see CmsJspNavBuilder#getNavigationForFolder() 117 * @see CmsJspNavBuilder#getNavigationTreeForFolder(int, int) 118 */ 119 public CmsJspNavElement() { 120 121 // empty 122 } 123 124 /** 125 * Create a new instance of the bean and calls the init method 126 * with the provided parameters.<p> 127 * 128 * @param sitePath will be passed to <code>init</code> 129 * @param resource the resource 130 * @param properties will be passed to <code>init</code> 131 */ 132 public CmsJspNavElement(String sitePath, CmsResource resource, Map<String, String> properties) { 133 134 setResource(resource); 135 init(sitePath, properties); 136 } 137 138 /** 139 * Create a new instance of the bean and calls the init method 140 * with the provided parameters.<p> 141 * 142 * @param sitePath will be passed to <code>init</code> 143 * @param resource the resource 144 * @param properties will be passed to <code>init</code> 145 * @param navTreeLevel will be passed to <code>init</code> 146 * 147 * @see #init(String, Map, int, Locale) 148 */ 149 public CmsJspNavElement(String sitePath, CmsResource resource, Map<String, String> properties, int navTreeLevel) { 150 151 this(sitePath, resource, properties, navTreeLevel, null); 152 } 153 154 /** 155 * Create a new instance of the bean and calls the init method 156 * with the provided parameters.<p> 157 * 158 * @param sitePath will be passed to <code>init</code> 159 * @param resource the resource 160 * @param properties will be passed to <code>init</code> 161 * @param navTreeLevel will be passed to <code>init</code> 162 * @param locale the locale for which properties should be accessed. 163 * 164 * @see #init(String, Map, int, Locale) 165 */ 166 public CmsJspNavElement( 167 String sitePath, 168 CmsResource resource, 169 Map<String, String> properties, 170 int navTreeLevel, 171 Locale locale) { 172 173 setResource(resource); 174 init(sitePath, properties, navTreeLevel, locale); 175 } 176 177 /** 178 * Create a new instance of the bean and calls the init method 179 * with the provided parameters.<p> 180 * 181 * @param sitePath will be passed to <code>init</code> 182 * @param properties will be passed to <code>init</code> 183 * 184 * @see #init(String, Map) 185 * 186 * @deprecated use {@link #CmsJspNavElement(String, CmsResource, Map)} 187 */ 188 @Deprecated 189 public CmsJspNavElement(String sitePath, Map<String, String> properties) { 190 191 init(sitePath, properties, -1, null); 192 } 193 194 /** 195 * Create a new instance of the bean and calls the init method 196 * with the provided parameters.<p> 197 * 198 * @param sitePath will be passed to <code>init</code> 199 * @param properties will be passed to <code>init</code> 200 * @param navTreeLevel will be passed to <code>init</code> 201 * 202 * @see #init(String, Map, int, Locale) 203 * 204 * @deprecated use {@link #CmsJspNavElement(String, CmsResource, Map, int)} 205 */ 206 @Deprecated 207 public CmsJspNavElement(String sitePath, Map<String, String> properties, int navTreeLevel) { 208 209 init(sitePath, properties, navTreeLevel, null); 210 } 211 212 /** 213 * Note: this class has a natural ordering that is inconsistent with equals.<p> 214 * 215 * @see java.lang.Comparable#compareTo(Object) 216 */ 217 public int compareTo(CmsJspNavElement obj) { 218 219 if (obj == this) { 220 return 0; 221 } 222 float pos = obj.getNavPosition(); 223 // please note: can't just subtract and cast to int here because of float precision loss 224 if (m_position == pos) { 225 return 0; 226 } 227 return (m_position < pos) ? -1 : 1; 228 } 229 230 /** 231 * Note: this class has a natural ordering that is inconsistent with equals.<p> 232 * 233 * @see java.lang.Object#equals(Object) 234 */ 235 @Override 236 public boolean equals(Object obj) { 237 238 if (obj == this) { 239 return true; 240 } 241 if (obj instanceof CmsJspNavElement) { 242 return ((CmsJspNavElement)obj).m_sitePath.equals(m_sitePath); 243 } 244 return false; 245 } 246 247 /** 248 * Returns the value of the property PROPERTY_DESCRIPTION of this navigation element, 249 * or <code>null</code> if this property is not set.<p> 250 * 251 * @return the value of the property PROPERTY_DESCRIPTION of this navigation element 252 * or <code>null</code> if this property is not set 253 */ 254 public String getDescription() { 255 256 return getProperties().get(CmsPropertyDefinition.PROPERTY_DESCRIPTION); 257 } 258 259 /** 260 * Returns the filename of the navigation element, i.e. 261 * the name of the navigation resource without any path information.<p> 262 * 263 * @return the filename of the navigation element, i.e. 264 * the name of the navigation resource without any path information 265 */ 266 public String getFileName() { 267 268 if (m_fileName == null) { 269 // use "lazy initializing" 270 if (!m_sitePath.endsWith("/")) { 271 m_fileName = m_sitePath.substring(m_sitePath.lastIndexOf("/") + 1, m_sitePath.length()); 272 } else { 273 m_fileName = m_sitePath.substring( 274 m_sitePath.substring(0, m_sitePath.length() - 1).lastIndexOf("/") + 1, 275 m_sitePath.length()); 276 } 277 } 278 return m_fileName; 279 } 280 281 /** 282 * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_NAVINFO}</code> of this 283 * navigation element, or <code>null</code> if this property is not set.<p> 284 * 285 * @return the value of the property or <code>null</code> if this property is not set 286 */ 287 public String getInfo() { 288 289 return getProperties().get(CmsPropertyDefinition.PROPERTY_NAVINFO); 290 } 291 292 /** 293 * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> of this 294 * navigation element, or <code>null</code> if this property is not set.<p> 295 * 296 * @return the value of the property or <code>null</code> if this property is not set 297 */ 298 public String getLocale() { 299 300 return getProperties().get(CmsPropertyDefinition.PROPERTY_LOCALE); 301 } 302 303 /** 304 * Returns the navigation builder context. 305 * 306 * @return the navigation builder context 307 */ 308 public NavContext getNavContext() { 309 310 return m_navContext; 311 } 312 313 /** 314 * Returns the value of the property <code>{@link CmsPropertyDefinition#PROPERTY_NAVIMAGE}</code> of this 315 * navigation element, or <code>null</code> if this property is not set.<p> 316 * 317 * @return the value of the property or <code>null</code> if this property is not set 318 */ 319 public String getNavImage() { 320 321 return getProperties().get(CmsPropertyDefinition.PROPERTY_NAVIMAGE); 322 } 323 324 /** 325 * Returns the value of the property C_PROPERTY_NAVPOS converted to a <code>float</code>, 326 * or a value of <code>Float.MAX_VALUE</code> if the navigation position property is not 327 * set (or not a valid number) for this resource.<p> 328 * 329 * @return float the value of the property C_PROPERTY_NAVPOS converted to a <code>float</code>, 330 * or a value of <code>Float.MAX_VALUE</code> if the navigation position property is not 331 * set (or not a valid number) for this resource 332 */ 333 public float getNavPosition() { 334 335 return m_position; 336 } 337 338 /** 339 * Returns the value of the property PROPERTY_NAVTEXT of this navigation element, 340 * or a warning message if this property is not set 341 * (this method will never return <code>null</code>).<p> 342 * 343 * @return the value of the property PROPERTY_NAVTEXT of this navigation element, 344 * or a warning message if this property is not set 345 * (this method will never return <code>null</code>) 346 */ 347 public String getNavText() { 348 349 if (m_text == null) { 350 // use "lazy initializing" 351 m_text = getProperties().get(CmsPropertyDefinition.PROPERTY_NAVTEXT); 352 if (m_text == null) { 353 m_text = CmsMessages.formatUnknownKey(CmsPropertyDefinition.PROPERTY_NAVTEXT); 354 } 355 } 356 return m_text; 357 } 358 359 /** 360 * Returns the navigation tree level of this resource.<p> 361 * 362 * @return the navigation tree level of this resource 363 */ 364 public int getNavTreeLevel() { 365 366 if (m_navTreeLevel == Integer.MIN_VALUE) { 367 // use "lazy initializing" 368 m_navTreeLevel = CmsResource.getPathLevel(m_sitePath); 369 } 370 return m_navTreeLevel; 371 } 372 373 /** 374 * Returns the name of the parent folder of the resource of this navigation element.<p> 375 * 376 * @return the name of the parent folder of the resource of this navigation element 377 */ 378 public String getParentFolderName() { 379 380 return CmsResource.getParentFolder(m_sitePath); 381 } 382 383 /** 384 * Returns the original map of all file properties of the resource that 385 * the navigation element belongs to.<p> 386 * 387 * Please note that the original reference is returned, so be careful when making 388 * changes to the map.<p> 389 * 390 * @return the original map of all file properties of the resource that 391 * the navigation element belongs to 392 */ 393 public Map<String, String> getProperties() { 394 395 if (null == m_locale) { 396 return m_properties; 397 } else { 398 return getLocaleProperties(); 399 } 400 } 401 402 /** 403 * Returns the value of the selected property from this navigation element.<p> 404 * 405 * The navigation element contains a hash of all file properties of the resource that 406 * the navigation element belongs to.<p> 407 * 408 * @param key the property name to look up 409 * 410 * @return the value of the selected property 411 */ 412 public String getProperty(String key) { 413 414 return getProperties().get(key); 415 } 416 417 /** 418 * Returns the resource.<p> 419 * 420 * @return the resource 421 */ 422 public CmsResource getResource() { 423 424 return m_resource; 425 } 426 427 /** 428 * Returns the resource name this navigation element was initialized with.<p> 429 * 430 * @return the resource name this navigation element was initialized with 431 */ 432 public String getResourceName() { 433 434 return m_sitePath; 435 } 436 437 /** 438 * Gets the sub-entries of the navigation entry. 439 * 440 * @return the sub-entries 441 */ 442 public List<CmsJspNavElement> getSubNavigation() { 443 444 if (m_subNavigation == null) { 445 if (m_resource.isFile()) { 446 m_subNavigation = Collections.emptyList(); 447 } else if (m_navContext == null) { 448 try { 449 throw new Exception("Can not get subnavigation because navigation context is not set."); 450 } catch (Exception e) { 451 LOG.warn(e.getLocalizedMessage(), e); 452 m_subNavigation = Collections.emptyList(); 453 } 454 } else { 455 CmsJspNavBuilder navBuilder = m_navContext.getNavBuilder(); 456 m_subNavigation = navBuilder.getNavigationForFolder( 457 navBuilder.getCmsObject().getSitePath(m_resource), 458 m_navContext.getVisibility(), 459 m_navContext.getFilter()); 460 } 461 462 } 463 return m_subNavigation; 464 465 } 466 467 /** 468 * Returns the value of the property PROPERTY_TITLE of this navigation element, 469 * or <code>null</code> if this property is not set.<p> 470 * 471 * @return the value of the property PROPERTY_TITLE of this navigation element 472 * or <code>null</code> if this property is not set 473 */ 474 public String getTitle() { 475 476 return getProperties().get(CmsPropertyDefinition.PROPERTY_TITLE); 477 } 478 479 /** 480 * Returns if the navigation position has been changed since initialization.<p> 481 * 482 * @return <code>true</code> if the navigation position has been changed since initialization 483 */ 484 public boolean hasChangedNavPosition() { 485 486 return m_changedNavPos; 487 } 488 489 /** 490 * Note: this class has a natural ordering that is inconsistent with equals.<p> 491 * 492 * @see java.lang.Object#hashCode() 493 */ 494 @Override 495 public int hashCode() { 496 497 return m_sitePath.hashCode(); 498 } 499 500 /** 501 * Same as calling {@link #init(String, Map, int, Locale) 502 * init(String, Hashtable, -1, null)}.<p> 503 * 504 * @param resource the name of the resource to extract the navigation 505 * information from 506 * @param properties the properties of the resource read from the vfs 507 */ 508 public void init(String resource, Map<String, String> properties) { 509 510 init(resource, properties, -1, null); 511 } 512 513 /** 514 * Initialized the member variables of this bean with the values 515 * provided.<p> 516 * 517 * A resource will be in the navigation if at least one of the two properties 518 * <code>I_CmsConstants.PROPERTY_NAVTEXT</code> or 519 * <code>I_CmsConstants.PROPERTY_NAVPOS</code> is set. Otherwise 520 * it will be ignored.<p> 521 * 522 * This bean does provides static methods to create a new instance 523 * from the context of a current CmsObject. Call these static methods 524 * in order to get a properly initialized bean.<p> 525 * 526 * @param resource the name of the resource to extract the navigation 527 * information from 528 * @param properties the properties of the resource read from the vfs 529 * @param navTreeLevel tree level of this resource, for building 530 * navigation trees 531 * 532 * @see CmsJspNavBuilder#getNavigationForResource() 533 */ 534 public void init(String resource, Map<String, String> properties, int navTreeLevel) { 535 536 init(resource, properties, navTreeLevel, null); 537 } 538 539 /** 540 * Initialized the member variables of this bean with the values 541 * provided.<p> 542 * 543 * A resource will be in the navigation if at least one of the two properties 544 * <code>I_CmsConstants.PROPERTY_NAVTEXT</code> or 545 * <code>I_CmsConstants.PROPERTY_NAVPOS</code> is set. Otherwise 546 * it will be ignored.<p> 547 * 548 * This bean does provides static methods to create a new instance 549 * from the context of a current CmsObject. Call these static methods 550 * in order to get a properly initialized bean.<p> 551 * 552 * @param resource the name of the resource to extract the navigation 553 * information from 554 * @param properties the properties of the resource read from the vfs 555 * @param navTreeLevel tree level of this resource, for building 556 * navigation trees 557 * @param locale The locale for which properties should be accessed. 558 * 559 * @see CmsJspNavBuilder#getNavigationForResource() 560 */ 561 public void init(String resource, Map<String, String> properties, int navTreeLevel, Locale locale) { 562 563 m_sitePath = resource; 564 m_properties = properties; 565 m_navTreeLevel = navTreeLevel; 566 m_locale = locale; 567 // init the position value 568 m_position = Float.MAX_VALUE; 569 try { 570 m_position = Float.parseFloat(getProperties().get(CmsPropertyDefinition.PROPERTY_NAVPOS)); 571 } catch (@SuppressWarnings("unused") Exception e) { 572 // m_position will have Float.MAX_VALUE, so navigation element will 573 // appear last in navigation 574 } 575 } 576 577 /** 578 * Returns <code>true</code> if this navigation element describes a folder, 579 * <code>false</code> otherwise.<p> 580 * 581 * @return <code>true</code> if this navigation element describes a folder, 582 * <code>false</code> otherwise.<p> 583 */ 584 public boolean isFolderLink() { 585 586 return m_sitePath.endsWith("/"); 587 } 588 589 /** 590 * Returns if this is a hidden navigation entry.<p> 591 * 592 * @return <code>true</code> if this is a hidden navigation entry 593 */ 594 public boolean isHiddenNavigationEntry() { 595 596 if (m_isHiddenNavigationEntry == null) { 597 // use "lazy initializing" 598 String navInfo = getProperties().get(CmsPropertyDefinition.PROPERTY_NAVINFO); 599 m_isHiddenNavigationEntry = Boolean.valueOf(CmsClientSitemapEntry.HIDDEN_NAVIGATION_ENTRY.equals(navInfo)); 600 } 601 return m_isHiddenNavigationEntry.booleanValue(); 602 } 603 604 /** 605 * Returns <code>true</code> if this navigation element is in the navigation, 606 * <code>false</code> otherwise.<p> 607 * 608 * A resource is considered to be in the navigation, if <ol> 609 * <li>it has the property PROPERTY_NAVTEXT set 610 * <li><em>or</em> it has the property PROPERTY_NAVPOS set 611 * <li><em>and</em> it is not a temporary file as defined by {@link CmsResource#isTemporaryFileName(String)}.</ol> 612 * 613 * @return <code>true</code> if this navigation element is in the navigation, <code>false</code> otherwise 614 */ 615 public boolean isInNavigation() { 616 617 if (m_hasNav == null) { 618 // use "lazy initializing" 619 Object o1 = getProperties().get(CmsPropertyDefinition.PROPERTY_NAVTEXT); 620 Object o2 = getProperties().get(CmsPropertyDefinition.PROPERTY_NAVPOS); 621 m_hasNav = Boolean.valueOf(((o1 != null) || (o2 != null)) && !CmsResource.isTemporaryFileName(m_sitePath)); 622 } 623 return m_hasNav.booleanValue(); 624 } 625 626 /** 627 * Returns if the navigation element represents a navigation level, linking to it's first sub-element.<p> 628 * 629 * @return <code>true</code> if the navigation element represents a navigation level 630 */ 631 public boolean isNavigationLevel() { 632 633 return CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals( 634 getProperties().get(CmsPropertyDefinition.PROPERTY_DEFAULT_FILE)); 635 } 636 637 /** 638 * Sets the navigation builder context. 639 * 640 * @param navContext the navigation builder context 641 */ 642 public void setNavContext(NavContext navContext) { 643 644 m_navContext = navContext; 645 } 646 647 /** 648 * Sets the value that will be returned by the {@link #getNavPosition()} 649 * method of this class.<p> 650 * 651 * @param value the value to set 652 */ 653 public void setNavPosition(float value) { 654 655 m_position = value; 656 m_changedNavPos = true; 657 } 658 659 /** 660 * Sets the navigation text.<p> 661 * 662 * @param text the text to set 663 */ 664 public void setNavText(String text) { 665 666 m_text = text; 667 } 668 669 /** 670 * Sets the navigation tree level.<p> 671 * 672 * @param navTreeLevel the navigation tree level to set 673 */ 674 public void setNavTreeLevel(int navTreeLevel) { 675 676 m_navTreeLevel = navTreeLevel; 677 } 678 679 /** 680 * @see java.lang.Object#toString() 681 */ 682 @Override 683 public String toString() { 684 685 StringBuffer result = new StringBuffer(); 686 687 result.append("["); 688 result.append(this.getClass().getName()); 689 result.append(", sitePath: "); 690 result.append(m_sitePath); 691 result.append(", navPosition: "); 692 result.append(getNavPosition()); 693 result.append(", navText "); 694 result.append(getNavText()); 695 result.append(", navTreeLevel: "); 696 result.append(getNavTreeLevel()); 697 result.append("]"); 698 699 return result.toString(); 700 } 701 702 /** 703 * Returns the site path of the target resource.<p> 704 * 705 * This may not be the same as the navigation resource.<p> 706 * 707 * @return the target resource site path 708 */ 709 protected String getSitePath() { 710 711 return m_sitePath; 712 } 713 714 /** 715 * Sets the resource.<p> 716 * 717 * @param resource the resource to set 718 */ 719 protected void setResource(CmsResource resource) { 720 721 m_resource = resource; 722 } 723 724 /** 725 * Helper to get locale specific properties. 726 * 727 * @return the locale specific properties map. 728 */ 729 private Map<String, String> getLocaleProperties() { 730 731 if (m_localeProperties == null) { 732 m_localeProperties = CmsCollectionsGenericWrapper.createLazyMap( 733 new CmsProperty.CmsPropertyLocaleTransformer(m_properties, m_locale)); 734 } 735 return m_localeProperties; 736 } 737 738}