001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.site.xmlsitemap; 029 030import org.opencms.ade.detailpage.CmsDetailPageInfo; 031import org.opencms.db.CmsAlias; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsPropertyDefinition; 035import org.opencms.file.CmsRequestContext; 036import org.opencms.file.CmsResource; 037import org.opencms.file.CmsResourceFilter; 038import org.opencms.file.CmsVfsResourceNotFoundException; 039import org.opencms.file.types.CmsResourceTypeHtmlRedirect; 040import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 041import org.opencms.file.types.I_CmsResourceType; 042import org.opencms.gwt.shared.alias.CmsAliasMode; 043import org.opencms.jsp.CmsJspNavBuilder; 044import org.opencms.jsp.CmsJspNavElement; 045import org.opencms.loader.CmsLoaderException; 046import org.opencms.loader.CmsResourceManager; 047import org.opencms.main.CmsException; 048import org.opencms.main.CmsLog; 049import org.opencms.main.OpenCms; 050import org.opencms.relations.CmsRelation; 051import org.opencms.relations.CmsRelationFilter; 052import org.opencms.relations.CmsRelationType; 053import org.opencms.site.CmsSite; 054import org.opencms.util.CmsFileUtil; 055import org.opencms.util.CmsStringUtil; 056import org.opencms.util.CmsUUID; 057 058import java.net.URI; 059import java.net.URISyntaxException; 060import java.util.ArrayList; 061import java.util.Collection; 062import java.util.HashMap; 063import java.util.HashSet; 064import java.util.Iterator; 065import java.util.LinkedHashMap; 066import java.util.List; 067import java.util.Locale; 068import java.util.Map; 069import java.util.Set; 070 071import org.apache.commons.logging.Log; 072 073import com.google.common.collect.ArrayListMultimap; 074import com.google.common.collect.Multimap; 075 076/** 077 * Class for generating XML sitemaps for SEO purposes, as described in 078 * <a href="http://www.sitemaps.org/protocol.html">http://www.sitemaps.org/protocol.html</a>.<p> 079 */ 080public class CmsXmlSitemapGenerator { 081 082 /** 083 * A bean that consists of a sitemap URL bean and a priority score, to determine which of multiple entries with the same 084 * URL are to be preferred.<p> 085 */ 086 protected class ResultEntry { 087 088 /** Internal priority to determine which of multiple entries with the same URL is used. 089 * Note that this has nothing to do with the priority in the URL bean itself! 090 */ 091 private int m_priority; 092 093 /** The URL bean. */ 094 private CmsXmlSitemapUrlBean m_urlBean; 095 096 /** 097 * Creates a new result entry.<p> 098 * 099 * @param urlBean the url bean 100 * 101 * @param priority the internal priority 102 */ 103 public ResultEntry(CmsXmlSitemapUrlBean urlBean, int priority) { 104 105 m_priority = priority; 106 m_urlBean = urlBean; 107 } 108 109 /** 110 * Gets the internal priority used to determine which of multiple entries with the same URL to use.<p> 111 * This has nothing to do with the priority defined in the URL beans themselves! 112 * 113 * @return the internal priority 114 */ 115 public int getPriority() { 116 117 return m_priority; 118 } 119 120 /** 121 * Gets the URL bean.<p> 122 * 123 * @return the URL bean 124 */ 125 public CmsXmlSitemapUrlBean getUrlBean() { 126 127 return m_urlBean; 128 } 129 } 130 131 /** The default change frequency. */ 132 public static final String DEFAULT_CHANGE_FREQUENCY = "daily"; 133 134 /** The default priority. */ 135 public static final double DEFAULT_PRIORITY = 0.5; 136 137 /** The logger instance for this class. */ 138 private static final Log LOG = CmsLog.getLog(CmsXmlSitemapGenerator.class); 139 140 /** The root path for the sitemap root folder. */ 141 protected String m_baseFolderRootPath; 142 143 /** The site path of the base folder. */ 144 protected String m_baseFolderSitePath; 145 146 /** Flag to control whether container page dates should be computed. */ 147 protected boolean m_computeContainerPageDates; 148 149 /** The list of detail page info beans. */ 150 protected List<CmsDetailPageInfo> m_detailPageInfos = new ArrayList<CmsDetailPageInfo>(); 151 152 /** A map from type names to lists of potential detail resources of that type. */ 153 protected Map<String, List<CmsResource>> m_detailResources = new HashMap<String, List<CmsResource>>(); 154 155 /** A multimap from detail page root paths to corresponding types. */ 156 protected Multimap<String, String> m_detailTypesByPage = ArrayListMultimap.create(); 157 158 /** A CMS context with guest privileges. */ 159 protected CmsObject m_guestCms; 160 161 /** The include/exclude configuration used for choosing pages for the XML sitemap. */ 162 protected CmsPathIncludeExcludeSet m_includeExcludeSet = new CmsPathIncludeExcludeSet(); 163 164 /** A map from structure ids to page aliases below the base folder which point to the given structure id. */ 165 protected Multimap<CmsUUID, CmsAlias> m_pageAliasesBelowBaseFolderByStructureId = ArrayListMultimap.create(); 166 167 /** The map used for storing the results, with URLs as keys. */ 168 protected Map<String, ResultEntry> m_resultMap = new LinkedHashMap<String, ResultEntry>(); 169 170 /** A guest user CMS object with the site root of the base folder. */ 171 protected CmsObject m_siteGuestCms; 172 173 /** The site root of the base folder. */ 174 protected String m_siteRoot; 175 176 /** A link to the site root. */ 177 protected String m_siteRootLink; 178 179 /** Configured replacement server URL. */ 180 private String m_serverUrl; 181 182 /** 183 * Creates a new sitemap generator instance.<p> 184 * 185 * @param folderRootPath the root folder for the XML sitemap to generate 186 * 187 * @throws CmsException if something goes wrong 188 */ 189 public CmsXmlSitemapGenerator(String folderRootPath) 190 throws CmsException { 191 192 m_baseFolderRootPath = CmsFileUtil.removeTrailingSeparator(folderRootPath); 193 m_guestCms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 194 m_siteGuestCms = OpenCms.initCmsObject(m_guestCms); 195 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(CmsStringUtil.joinPaths(folderRootPath, "/")); 196 m_siteRoot = site.getSiteRoot(); 197 198 m_siteGuestCms.getRequestContext().setSiteRoot(m_siteRoot); 199 m_baseFolderSitePath = CmsStringUtil.joinPaths( 200 "/", 201 m_siteGuestCms.getRequestContext().removeSiteRoot(m_baseFolderRootPath)); 202 } 203 204 /** 205 * Replaces the protocol/host/port of a link with the ones from the given server URI, if it's not empty.<p> 206 * 207 * @param link the link to change 208 * @param server the server URI string 209 210 * @return the changed link 211 */ 212 public static String replaceServerUri(String link, String server) { 213 214 String serverUriStr = server; 215 216 if (CmsStringUtil.isEmptyOrWhitespaceOnly(serverUriStr)) { 217 return link; 218 } 219 try { 220 URI serverUri = new URI(serverUriStr); 221 URI linkUri = new URI(link); 222 URI result = new URI( 223 serverUri.getScheme(), 224 serverUri.getAuthority(), 225 linkUri.getPath(), 226 linkUri.getQuery(), 227 linkUri.getFragment()); 228 return result.toString(); 229 } catch (URISyntaxException e) { 230 LOG.error(e.getLocalizedMessage(), e); 231 return link; 232 } 233 234 } 235 236 /** 237 * Gets the change frequency for a sitemap entry from a list of properties.<p> 238 * 239 * If the change frequency is not defined in the properties, this method will return null.<p> 240 * 241 * @param properties the properties from which the change frequency should be obtained 242 * 243 * @return the change frequency string 244 */ 245 protected static String getChangeFrequency(List<CmsProperty> properties) { 246 247 CmsProperty prop = CmsProperty.get(CmsPropertyDefinition.PROPERTY_XMLSITEMAP_CHANGEFREQ, properties); 248 if (prop.isNullProperty()) { 249 return null; 250 } 251 String result = prop.getValue().trim(); 252 return result; 253 } 254 255 /** 256 * Gets the page priority from a list of properties.<p> 257 * 258 * If the page priority can't be found among the properties, -1 will be returned.<p> 259 * 260 * @param properties the properties of a resource 261 * 262 * @return the page priority read from the properties, or -1 263 */ 264 protected static double getPriority(List<CmsProperty> properties) { 265 266 CmsProperty prop = CmsProperty.get(CmsPropertyDefinition.PROPERTY_XMLSITEMAP_PRIORITY, properties); 267 if (prop.isNullProperty()) { 268 return -1.0; 269 } 270 try { 271 double result = Double.parseDouble(prop.getValue().trim()); 272 return result; 273 } catch (NumberFormatException e) { 274 return -1.0; 275 } 276 } 277 278 /** 279 * Removes files marked as internal from a resource list.<p> 280 * 281 * @param resources the list which should be replaced 282 */ 283 protected static void removeInternalFiles(List<CmsResource> resources) { 284 285 Iterator<CmsResource> iter = resources.iterator(); 286 while (iter.hasNext()) { 287 CmsResource resource = iter.next(); 288 if (resource.isInternal()) { 289 iter.remove(); 290 } 291 } 292 } 293 294 /** 295 * Generates a list of XML sitemap entry beans for the root folder which has been set in the constructor.<p> 296 * 297 * @return the list of XML sitemap entries 298 * 299 * @throws CmsException if something goes wrong 300 */ 301 public List<CmsXmlSitemapUrlBean> generateSitemapBeans() throws CmsException { 302 303 String baseSitePath = m_siteGuestCms.getRequestContext().removeSiteRoot(m_baseFolderRootPath); 304 initializeFileData(baseSitePath); 305 for (CmsResource resource : getDirectPages()) { 306 if (CmsResourceTypeHtmlRedirect.isRedirect(resource)) { 307 continue; 308 } 309 String sitePath = m_siteGuestCms.getSitePath(resource); 310 List<CmsProperty> propertyList = m_siteGuestCms.readPropertyObjects(resource, true); 311 String onlineLink = OpenCms.getLinkManager().getOnlineLink(m_siteGuestCms, sitePath); 312 boolean isContainerPage = CmsResourceTypeXmlContainerPage.isContainerPage(resource); 313 long dateModified = resource.getDateLastModified(); 314 if (isContainerPage) { 315 if (m_computeContainerPageDates) { 316 dateModified = computeContainerPageModificationDate(resource); 317 } else { 318 dateModified = -1; 319 } 320 } 321 CmsXmlSitemapUrlBean urlBean = new CmsXmlSitemapUrlBean( 322 replaceServerUri(onlineLink), 323 dateModified, 324 getChangeFrequency(propertyList), 325 getPriority(propertyList)); 326 urlBean.setOriginalResource(resource); 327 addResult(urlBean, 3); 328 if (isContainerPage) { 329 Locale locale = getLocale(resource, propertyList); 330 addDetailLinks(resource, locale); 331 } 332 } 333 334 for (CmsUUID aliasStructureId : m_pageAliasesBelowBaseFolderByStructureId.keySet()) { 335 addAliasLinks(aliasStructureId); 336 } 337 338 List<CmsXmlSitemapUrlBean> result = new ArrayList<CmsXmlSitemapUrlBean>(); 339 for (ResultEntry resultEntry : m_resultMap.values()) { 340 result.add(resultEntry.getUrlBean()); 341 } 342 return result; 343 } 344 345 /** 346 * Gets the include/exclude configuration of this XML sitemap generator.<p> 347 * 348 * @return the include/exclude configuration 349 */ 350 public CmsPathIncludeExcludeSet getIncludeExcludeSet() { 351 352 return m_includeExcludeSet; 353 } 354 355 /** 356 * Generates a sitemap and formats it as a string.<p> 357 * 358 * @return the sitemap XML data 359 * 360 * @throws CmsException if something goes wrong 361 */ 362 public String renderSitemap() throws CmsException { 363 364 StringBuffer buffer = new StringBuffer(); 365 List<CmsXmlSitemapUrlBean> urlBeans = generateSitemapBeans(); 366 buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 367 buffer.append(getUrlSetOpenTag() + "\n"); 368 for (CmsXmlSitemapUrlBean bean : urlBeans) { 369 buffer.append(getXmlForEntry(bean)); 370 buffer.append("\n"); 371 } 372 buffer.append("</urlset>"); 373 return buffer.toString(); 374 } 375 376 /** 377 * Enables or disables computation of container page dates.<p> 378 * 379 * @param computeContainerPageDates the new value 380 */ 381 public void setComputeContainerPageDates(boolean computeContainerPageDates) { 382 383 m_computeContainerPageDates = computeContainerPageDates; 384 } 385 386 /** 387 * Sets the replacement server URL.<p> 388 * 389 * The replacement server URL will replace the scheme/host/port from the URLs returned by getOnlineLink. 390 * 391 * @param serverUrl the server URL 392 */ 393 public void setServerUrl(String serverUrl) { 394 395 m_serverUrl = serverUrl; 396 } 397 398 /** 399 * Adds the detail page links for a given page to the results.<p> 400 * 401 * @param containerPage the container page resource 402 * @param locale the locale of the container page 403 * 404 * @throws CmsException if something goes wrong 405 */ 406 protected void addDetailLinks(CmsResource containerPage, Locale locale) throws CmsException { 407 408 List<I_CmsResourceType> types = getDetailTypesForPage(containerPage); 409 for (I_CmsResourceType type : types) { 410 List<CmsResource> resourcesForType = getDetailResources(type); 411 for (CmsResource detailRes : resourcesForType) { 412 if (!isValidDetailPageCombination(containerPage, locale, detailRes)) { 413 continue; 414 } 415 List<CmsProperty> detailProps = m_guestCms.readPropertyObjects(detailRes, true); 416 String detailLink = getDetailLink(containerPage, detailRes, locale); 417 CmsXmlSitemapUrlBean detailUrlBean = new CmsXmlSitemapUrlBean( 418 replaceServerUri(detailLink), 419 detailRes.getDateLastModified(), 420 getChangeFrequency(detailProps), 421 getPriority(detailProps)); 422 detailUrlBean.setOriginalResource(detailRes); 423 detailUrlBean.setDetailPageResource(containerPage); 424 addResult(detailUrlBean, 2); 425 } 426 } 427 } 428 429 /** 430 * Adds an URL bean to the internal map of results, but only if there is no existing entry with higher internal priority 431 * than the priority given as an argument.<p> 432 * 433 * @param result the result URL bean to add 434 * 435 * @param resultPriority the internal priority to use for updating the map of results 436 */ 437 protected void addResult(CmsXmlSitemapUrlBean result, int resultPriority) { 438 439 String url = CmsFileUtil.removeTrailingSeparator(result.getUrl()); 440 boolean writeEntry = true; 441 if (m_resultMap.containsKey(url)) { 442 LOG.warn("Encountered duplicate URL with while generating sitemap: " + result.getUrl()); 443 ResultEntry entry = m_resultMap.get(url); 444 writeEntry = entry.getPriority() <= resultPriority; 445 } 446 if (writeEntry) { 447 m_resultMap.put(url, new ResultEntry(result, resultPriority)); 448 } 449 } 450 451 /** 452 * Computes the container the container page modification date from its referenced contents.<p> 453 * 454 * @param containerPage the container page 455 * 456 * @return the computed modification date 457 * 458 * @throws CmsException if something goes wrong 459 */ 460 protected long computeContainerPageModificationDate(CmsResource containerPage) throws CmsException { 461 462 CmsRelationFilter filter = CmsRelationFilter.relationsFromStructureId( 463 containerPage.getStructureId()).filterType(CmsRelationType.XML_STRONG); 464 List<CmsRelation> relations = m_guestCms.readRelations(filter); 465 long result = containerPage.getDateLastModified(); 466 for (CmsRelation relation : relations) { 467 try { 468 CmsResource target = relation.getTarget( 469 m_guestCms, 470 CmsResourceFilter.DEFAULT_FILES.addRequireVisible()); 471 long targetDate = target.getDateLastModified(); 472 if (targetDate > result) { 473 result = targetDate; 474 } 475 } catch (CmsException e) { 476 LOG.warn( 477 "Could not get relation target for relation " 478 + relation.toString() 479 + " | " 480 + e.getLocalizedMessage(), 481 e); 482 } 483 } 484 485 return result; 486 } 487 488 /** 489 * Gets the detail link for a given container page and detail content.<p> 490 * 491 * Note: The actual container page used for the result link is not necessarily the container page passed 492 * in as parameter - the default detail page in the sitemap containing the page is used. 493 * 494 * @param pageRes the container page 495 * @param detailRes the detail content 496 * @param locale the locale for which we want the link 497 * 498 * @return the detail page link 499 */ 500 protected String getDetailLink(CmsResource pageRes, CmsResource detailRes, Locale locale) { 501 502 String pageSitePath = m_siteGuestCms.getSitePath(pageRes); 503 String detailSitePath = m_siteGuestCms.getSitePath(detailRes); 504 CmsRequestContext requestContext = m_siteGuestCms.getRequestContext(); 505 String originalUri = requestContext.getUri(); 506 Locale originalLocale = requestContext.getLocale(); 507 try { 508 requestContext.setUri(pageSitePath); 509 requestContext.setLocale(locale); 510 return OpenCms.getLinkManager().getOnlineLink(m_siteGuestCms, detailSitePath, true); 511 } finally { 512 requestContext.setUri(originalUri); 513 requestContext.setLocale(originalLocale); 514 } 515 } 516 517 /** 518 * Gets the types for which a given resource is configured as a detail page.<p> 519 * 520 * @param resource a resource for which we want to find the detail page types 521 * 522 * @return the list of resource types for which the given page is configured as a detail page 523 */ 524 protected List<I_CmsResourceType> getDetailTypesForPage(CmsResource resource) { 525 526 Collection<String> typesForPage = m_detailTypesByPage.get(resource.getRootPath()); 527 String parentPath = CmsFileUtil.removeTrailingSeparator(CmsResource.getParentFolder(resource.getRootPath())); 528 Collection<String> typesForFolder = m_detailTypesByPage.get(parentPath); 529 Set<String> allTypes = new HashSet<String>(); 530 allTypes.addAll(typesForPage); 531 allTypes.addAll(typesForFolder); 532 List<I_CmsResourceType> resTypes = new ArrayList<I_CmsResourceType>(); 533 CmsResourceManager resMan = OpenCms.getResourceManager(); 534 for (String typeName : allTypes) { 535 if (typeName.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) { 536 continue; 537 } 538 try { 539 I_CmsResourceType resType = resMan.getResourceType(typeName); 540 resTypes.add(resType); 541 } catch (CmsLoaderException e) { 542 LOG.warn("Invalid resource type name" + typeName + "! " + e.getLocalizedMessage(), e); 543 } 544 } 545 return resTypes; 546 } 547 548 /** 549 * Gets the list of pages which should be directly added to the XML sitemap.<p> 550 * 551 * @return the list of resources which should be directly added to the XML sitemap 552 * 553 * @throws CmsException if something goes wrong 554 */ 555 protected List<CmsResource> getDirectPages() throws CmsException { 556 557 List<CmsResource> result = new ArrayList<CmsResource>(); 558 result.addAll(getNavigationPages()); 559 Set<String> includeRoots = m_includeExcludeSet.getIncludeRoots(); 560 for (String includeRoot : includeRoots) { 561 try { 562 CmsResource resource = m_guestCms.readResource(includeRoot); 563 if (resource.isFile()) { 564 result.add(resource); 565 } else { 566 List<CmsResource> subtreeFiles = m_guestCms.readResources( 567 includeRoot, 568 CmsResourceFilter.DEFAULT_FILES, 569 true); 570 result.addAll(subtreeFiles); 571 } 572 } catch (CmsVfsResourceNotFoundException e) { 573 LOG.warn("Could not read include resource: " + includeRoot); 574 } 575 } 576 Iterator<CmsResource> filterIter = result.iterator(); 577 while (filterIter.hasNext()) { 578 CmsResource currentResource = filterIter.next(); 579 if (currentResource.isInternal() || m_includeExcludeSet.isExcluded(currentResource.getRootPath())) { 580 filterIter.remove(); 581 } 582 } 583 return result; 584 } 585 586 /** 587 * Writes the inner node content for an url element to a buffer.<p> 588 * 589 * @param entry the entry for which the content should be written 590 * @return the inner XML 591 */ 592 protected String getInnerXmlForEntry(CmsXmlSitemapUrlBean entry) { 593 594 StringBuffer buffer = new StringBuffer(); 595 entry.writeElement(buffer, "loc", entry.getUrl()); 596 entry.writeLastmod(buffer); 597 entry.writeChangefreq(buffer); 598 entry.writePriority(buffer); 599 return buffer.toString(); 600 } 601 602 /** 603 * Gets the list of pages from the navigation which should be directly added to the XML sitemap.<p> 604 * 605 * @return the list of pages to add to the XML sitemap 606 */ 607 protected List<CmsResource> getNavigationPages() { 608 609 List<CmsResource> result = new ArrayList<CmsResource>(); 610 CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(m_siteGuestCms); 611 try { 612 CmsResource rootDefaultFile = m_siteGuestCms.readDefaultFile( 613 m_siteGuestCms.getRequestContext().removeSiteRoot(m_baseFolderRootPath), 614 CmsResourceFilter.DEFAULT); 615 if (rootDefaultFile != null) { 616 result.add(rootDefaultFile); 617 } 618 } catch (Exception e) { 619 LOG.info(e.getLocalizedMessage(), e); 620 } 621 List<CmsJspNavElement> navElements = navBuilder.getSiteNavigation( 622 m_baseFolderSitePath, 623 CmsJspNavBuilder.Visibility.includeHidden, 624 -1); 625 for (CmsJspNavElement navElement : navElements) { 626 CmsResource navResource = navElement.getResource(); 627 if (navResource.isFolder()) { 628 try { 629 CmsResource defaultFile = m_guestCms.readDefaultFile(navResource, CmsResourceFilter.DEFAULT_FILES); 630 if (defaultFile != null) { 631 result.add(defaultFile); 632 } else { 633 LOG.warn("Could not get default file for " + navResource.getRootPath()); 634 } 635 } catch (CmsException e) { 636 LOG.warn("Could not get default file for " + navResource.getRootPath()); 637 } 638 } else { 639 result.add(navResource); 640 } 641 } 642 return result; 643 } 644 645 /** 646 * Gets the opening tag for the urlset element (can be overridden to add e.g. more namespaces.<p> 647 * 648 * @return the opening tag 649 */ 650 protected String getUrlSetOpenTag() { 651 652 return "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"; 653 } 654 655 /** 656 * Writes the XML for an URL entry to a buffer.<p> 657 * 658 * @param entry the XML sitemap entry bean 659 * 660 * @return an XML representation of this bean 661 */ 662 protected String getXmlForEntry(CmsXmlSitemapUrlBean entry) { 663 664 StringBuffer buffer = new StringBuffer(); 665 buffer.append("<url>"); 666 buffer.append(getInnerXmlForEntry(entry)); 667 buffer.append("</url>"); 668 return buffer.toString(); 669 } 670 671 /** 672 * Checks whether the given alias is below the base folder.<p> 673 * 674 * @param alias the alias to check 675 * 676 * @return true if the alias is below the base folder 677 */ 678 protected boolean isAliasBelowBaseFolder(CmsAlias alias) { 679 680 boolean isBelowBaseFolder = CmsStringUtil.isPrefixPath(m_baseFolderSitePath, alias.getAliasPath()); 681 return isBelowBaseFolder; 682 } 683 684 /** 685 * Checks whether the page/detail content combination is a valid detail page.<p> 686 * 687 * @param page the container page 688 * @param locale the locale 689 * @param detailRes the detail content resource 690 * 691 * @return true if this is a valid detail page combination 692 */ 693 protected boolean isValidDetailPageCombination(CmsResource page, Locale locale, CmsResource detailRes) { 694 695 return OpenCms.getADEManager().getDetailPageHandler().isValidDetailPage(m_guestCms, page, detailRes); 696 } 697 698 /** 699 * Replaces the protocol/host/port of a link with the ones from the configured server URI, if it's not empty.<p> 700 * 701 * @param link the link to change 702 * 703 * @return the changed link 704 */ 705 protected String replaceServerUri(String link) { 706 707 return replaceServerUri(link, m_serverUrl); 708 } 709 710 /** 711 * Adds the alias links for a given structure id to the results.<p> 712 * 713 * @param aliasStructureId the alias target structure id 714 */ 715 private void addAliasLinks(CmsUUID aliasStructureId) { 716 717 try { 718 CmsResource aliasTarget = m_guestCms.readResource(aliasStructureId); 719 List<CmsProperty> properties = m_guestCms.readPropertyObjects(aliasTarget, true); 720 double priority = getPriority(properties); 721 String changeFrequency = getChangeFrequency(properties); 722 Collection<CmsAlias> aliases = m_pageAliasesBelowBaseFolderByStructureId.get(aliasStructureId); 723 for (CmsAlias alias : aliases) { 724 String aliasLink = (m_siteRootLink + "/" + alias.getAliasPath()).replaceAll("(?<!:)//+", "/"); 725 CmsXmlSitemapUrlBean aliasUrlBean = new CmsXmlSitemapUrlBean( 726 replaceServerUri(aliasLink), 727 -1, 728 changeFrequency, 729 priority); 730 aliasUrlBean.setOriginalResource(aliasTarget); 731 addResult(aliasUrlBean, 1); 732 } 733 } catch (CmsException e) { 734 LOG.error(e.getLocalizedMessage(), e); 735 } 736 } 737 738 /** 739 * Gets all resources from the folder tree beneath the base folder or the shared folder which have a given type.<p> 740 * 741 * @param type the type to filter by 742 * 743 * @return the list of resources with the given type 744 * 745 * @throws CmsException if something goes wrong 746 */ 747 private List<CmsResource> getDetailResources(I_CmsResourceType type) throws CmsException { 748 749 String typeName = type.getTypeName(); 750 if (!m_detailResources.containsKey(typeName)) { 751 List<CmsResource> result = new ArrayList<CmsResource>(); 752 CmsResourceFilter filter = CmsResourceFilter.DEFAULT_FILES.addRequireType(type); 753 List<CmsResource> siteFiles = m_guestCms.readResources(m_siteRoot, filter, true); 754 result.addAll(siteFiles); 755 String shared = CmsFileUtil.removeTrailingSeparator(OpenCms.getSiteManager().getSharedFolder()); 756 if (shared != null) { 757 List<CmsResource> sharedFiles = m_guestCms.readResources(shared, filter, true); 758 result.addAll(sharedFiles); 759 } 760 m_detailResources.put(typeName, result); 761 } 762 return m_detailResources.get(typeName); 763 } 764 765 /** 766 * Gets the locale to use for the given resource.<p> 767 * 768 * @param resource the resource 769 * @param propertyList the properties of the resource 770 * 771 * @return the locale to use for the given resource 772 */ 773 private Locale getLocale(CmsResource resource, List<CmsProperty> propertyList) { 774 775 return OpenCms.getLocaleManager().getDefaultLocale(m_guestCms, m_guestCms.getSitePath(resource)); 776 } 777 778 /** 779 * Reads the data necessary for building the sitemap from the VFS and initializes the internal data structures.<p> 780 * 781 * @param baseSitePath the base site path 782 * 783 * @throws CmsException if something goes wrong 784 */ 785 private void initializeFileData(String baseSitePath) throws CmsException { 786 787 m_resultMap.clear(); 788 m_siteRootLink = OpenCms.getLinkManager().getOnlineLink(m_siteGuestCms, "/"); 789 m_siteRootLink = CmsFileUtil.removeTrailingSeparator(m_siteRootLink); 790 m_detailPageInfos = OpenCms.getADEManager().getAllDetailPages(m_guestCms); 791 for (CmsDetailPageInfo detailPageInfo : m_detailPageInfos) { 792 String type = detailPageInfo.getType(); 793 String path = detailPageInfo.getUri(); 794 path = CmsFileUtil.removeTrailingSeparator(path); 795 m_detailTypesByPage.put(path, type); 796 } 797 List<CmsAlias> siteAliases = OpenCms.getAliasManager().getAliasesForSite( 798 m_siteGuestCms, 799 m_siteGuestCms.getRequestContext().getSiteRoot()); 800 for (CmsAlias alias : siteAliases) { 801 if (isAliasBelowBaseFolder(alias) && (alias.getMode() == CmsAliasMode.page)) { 802 CmsUUID aliasId = alias.getStructureId(); 803 m_pageAliasesBelowBaseFolderByStructureId.put(aliasId, alias); 804 } 805 } 806 807 } 808 809}