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