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.ade.detailpage; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsADEManager; 032import org.opencms.configuration.CmsParameterConfiguration; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsVfsResourceNotFoundException; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.site.CmsSite; 040import org.opencms.util.CmsFileUtil; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.workplace.CmsWorkplace; 043 044import java.util.ArrayList; 045import java.util.Arrays; 046import java.util.Collection; 047import java.util.List; 048 049import org.apache.commons.lang3.RandomStringUtils; 050import org.apache.commons.logging.Log; 051 052/** 053 * This class uses information from the detail page information stored in the sitemap to find/recognize the detail pages for 054 * a given resource.<p> 055 * 056 * @since 8.0.0 057 */ 058public class CmsDefaultDetailPageHandler implements I_CmsDetailPageHandler { 059 060 /** 061 * Helper class for storing intermediate results when looking up detail pages for a resource. 062 */ 063 private static class DetailPageConfigData { 064 065 /** The link source config. */ 066 private CmsADEConfigData m_sourceConfig; 067 068 /** The configuration whose detail pages are used. */ 069 private CmsADEConfigData m_configForDetailPages; 070 071 /** The link target configuration. */ 072 private CmsADEConfigData m_targetConfig; 073 074 /** The detail pages. */ 075 private List<CmsDetailPageInfo> m_detailPages = new ArrayList<>(); 076 077 /** 078 * Gets the config for detail pages. 079 * 080 * @return the config for detail pages 081 */ 082 public CmsADEConfigData getConfigForDetailPages() { 083 084 return m_configForDetailPages; 085 } 086 087 /** 088 * Gets the detail pages. 089 * 090 * @return the detail pages 091 */ 092 public List<CmsDetailPageInfo> getDetailPages() { 093 094 return m_detailPages; 095 } 096 097 /** 098 * Gets the source config. 099 * 100 * @return the source config 101 */ 102 @SuppressWarnings("unused") 103 public CmsADEConfigData getSourceConfig() { 104 105 return m_sourceConfig; 106 } 107 108 /** 109 * Gets the target config. 110 * 111 * @return the target config 112 */ 113 @SuppressWarnings("unused") 114 public CmsADEConfigData getTargetConfig() { 115 116 return m_targetConfig; 117 } 118 119 /** 120 * Sets the config for detail pages. 121 * 122 * @param configForDetailPages the new config for detail pages 123 */ 124 public void setConfigForDetailPages(CmsADEConfigData configForDetailPages) { 125 126 m_configForDetailPages = configForDetailPages; 127 } 128 129 /** 130 * Sets the detail pages. 131 * 132 * @param detailPages the new detail pages 133 */ 134 public void setDetailPages(List<CmsDetailPageInfo> detailPages) { 135 136 m_detailPages = detailPages; 137 } 138 139 /** 140 * Sets the source config. 141 * 142 * @param sourceConfig the new source config 143 */ 144 public void setSourceConfig(CmsADEConfigData sourceConfig) { 145 146 m_sourceConfig = sourceConfig; 147 } 148 149 /** 150 * Sets the target config. 151 * 152 * @param targetConfig the new target config 153 */ 154 public void setTargetConfig(CmsADEConfigData targetConfig) { 155 156 m_targetConfig = targetConfig; 157 } 158 159 } 160 161 /** Logger instance for this class. */ 162 private static final Log LOG = CmsLog.getLog(CmsDefaultDetailPageHandler.class); 163 164 /** The configuration. */ 165 private CmsParameterConfiguration m_config = new CmsParameterConfiguration(); 166 167 /** 168 * Constructor. 169 */ 170 public CmsDefaultDetailPageHandler() { 171 172 // just for debugging 173 LOG.debug("Created default detail page handler."); 174 } 175 176 /** 177 * Adds the configuration parameter. 178 * 179 * @param paramName the param name 180 * @param paramValue the param value 181 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 182 */ 183 public void addConfigurationParameter(String paramName, String paramValue) { 184 185 m_config.add(paramName, paramValue); 186 187 } 188 189 /** 190 * Gets the all detail pages. 191 * 192 * @param cms the cms 193 * @param resType the res type 194 * @return the all detail pages 195 * @throws CmsException the cms exception 196 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#getAllDetailPages(org.opencms.file.CmsObject, int) 197 */ 198 public Collection<String> getAllDetailPages(CmsObject cms, int resType) throws CmsException { 199 200 if (!OpenCms.getADEManager().isInitialized()) { 201 return new ArrayList<String>(); 202 } 203 String typeName = OpenCms.getResourceManager().getResourceType(resType).getTypeName(); 204 return OpenCms.getADEManager().getDetailPages(cms, typeName); 205 } 206 207 /** 208 * Gets the configuration. 209 * 210 * @return the configuration 211 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 212 */ 213 public CmsParameterConfiguration getConfiguration() { 214 215 return m_config; 216 217 } 218 219 /** 220 * Gets the detail page for a content element.<p> 221 * 222 * @param manager the ADE manager instance. 223 * @param cms the CMS context 224 * @param contentRootPath the element's root path 225 * @param originPath the site path from which the detail content is being linked 226 * @param targetDetailPage the target detail page to use 227 * 228 * @return the detail page for the content element 229 */ 230 public String getDetailPage( 231 CmsADEManager manager, 232 CmsObject cms, 233 String contentRootPath, 234 String originPath, 235 String targetDetailPage) { 236 237 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 238 String resType = manager.getParentFolderType(online, contentRootPath); 239 // resType may not actually be the resource type of the resource at contentRootPath. We determine 240 // the actual resource type further below, but if getParentFolderType() returns null here, we can stop 241 // without reading any resources. 242 if (resType == null) { 243 return null; 244 } 245 246 if (targetDetailPage != null) { 247 try { 248 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(targetDetailPage); 249 CmsResource targetDetailPageRes = null; 250 if (site != null) { 251 CmsObject rootCms = OpenCms.initCmsObject(cms); 252 rootCms.getRequestContext().setSiteRoot(""); 253 targetDetailPageRes = rootCms.readResource(targetDetailPage); 254 } else { 255 targetDetailPageRes = cms.readResource(targetDetailPage); 256 } 257 if (manager.isDetailPage(cms, targetDetailPageRes)) { 258 return targetDetailPageRes.getRootPath(); 259 } 260 } catch (CmsVfsResourceNotFoundException e) { 261 LOG.debug(e.getLocalizedMessage(), e); 262 } catch (Exception e) { 263 LOG.warn(e.getLocalizedMessage(), e); 264 } 265 } 266 267 try { 268 CmsObject rootCms = OpenCms.initCmsObject(cms); 269 rootCms.getRequestContext().setSiteRoot(""); 270 CmsResource detailResource = rootCms.readResource(contentRootPath); 271 resType = OpenCms.getResourceManager().getResourceType(detailResource).getTypeName(); 272 } catch (CmsVfsResourceNotFoundException e) { 273 LOG.info(e.getLocalizedMessage(), e); 274 } catch (Exception e) { 275 LOG.warn(e.getLocalizedMessage(), e); 276 } 277 DetailPageConfigData context = lookupDetailPageConfigData(manager, cms, contentRootPath, originPath, resType); 278 List<CmsDetailPageInfo> relevantPages = context.getDetailPages(); 279 if (context.getConfigForDetailPages() == null) { 280 return null; 281 } 282 LOG.info( 283 "Trying to determine detail page for '" 284 + contentRootPath 285 + "' in context '" 286 + context.getConfigForDetailPages().getBasePath() 287 + "'"); 288 if (!CmsStringUtil.isPrefixPath( 289 context.getConfigForDetailPages().getExternalDetailContentExclusionFolder(), 290 contentRootPath)) { 291 return null; 292 } 293 String result = new CmsDetailPageFilter(cms, contentRootPath).filterDetailPages(relevantPages).map( 294 info -> info.getUri()).findFirst().orElse(null); 295 return result; 296 } 297 298 /** 299 * Gets the detail page to use for a detail resource. 300 * 301 * @param cms the cms 302 * @param rootPath the root path 303 * @param linkSource the link source 304 * @param targetDetailPage the target detail page 305 * @return the detail page 306 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#getDetailPage(org.opencms.file.CmsObject, java.lang.String, java.lang.String, java.lang.String) 307 */ 308 public String getDetailPage(CmsObject cms, String rootPath, String linkSource, String targetDetailPage) { 309 310 CmsADEManager manager = OpenCms.getADEManager(); 311 if (!manager.isInitialized()) { 312 return null; 313 } 314 315 if (rootPath.endsWith(".jsp") || rootPath.startsWith(CmsWorkplace.VFS_PATH_WORKPLACE)) { 316 // exclude these for performance reasons 317 return null; 318 } 319 String result = getDetailPage(manager, cms, rootPath, linkSource, targetDetailPage); 320 if (result == null) { 321 return null; 322 } 323 if (!CmsResource.isFolder(result)) { 324 result = CmsResource.getFolderPath(result); 325 } 326 return result; 327 } 328 329 /** 330 * Inits the configuration. 331 * 332 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 333 */ 334 public void initConfiguration() { 335 336 m_config = CmsParameterConfiguration.unmodifiableVersion(m_config); 337 338 } 339 340 /** 341 * Initialize. 342 * 343 * @param offlineCms the offline cms 344 * @param onlineCms the online cms 345 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#initialize(org.opencms.file.CmsObject, org.opencms.file.CmsObject) 346 */ 347 public void initialize(CmsObject offlineCms, CmsObject onlineCms) { 348 349 // do nothing 350 } 351 352 /** 353 * Checks whether the given detail page is valid for the given resource.<p> 354 * 355 * @param cms the CMS context 356 * @param page the detail page 357 * @param detailRes the detail resource 358 * 359 * @return true if the given detail page is valid 360 */ 361 public boolean isValidDetailPage(CmsObject cms, CmsResource page, CmsResource detailRes) { 362 363 String p = "[" + RandomStringUtils.randomAlphanumeric(6) + "] "; 364 LOG.debug(p + "isValidDetailPage(" + page.getRootPath() + "," + detailRes.getRootPath() + ")"); 365 if (OpenCms.getSystemInfo().isRestrictDetailContents()) { 366 // in 'restrict detail contents mode', do not allow detail contents from a real site on a detail page of a different real site 367 CmsSite pageSite = OpenCms.getSiteManager().getSiteForRootPath(page.getRootPath()); 368 CmsSite detailSite = OpenCms.getSiteManager().getSiteForRootPath(detailRes.getRootPath()); 369 if ((pageSite != null) 370 && (detailSite != null) 371 && !pageSite.getSiteRoot().equals(detailSite.getSiteRoot())) { 372 LOG.debug(p + "returned false because of restrict-detail-contents option"); 373 return false; 374 } 375 } 376 377 if (!OpenCms.getADEManager().isDetailPage(cms, page)) { 378 LOG.debug(p + "returned false because the page is not a detail page."); 379 return false; 380 } 381 382 String typeName = OpenCms.getResourceManager().getResourceType(detailRes).getTypeName(); 383 DetailPageConfigData context = lookupDetailPageConfigData( 384 OpenCms.getADEManager(), 385 cms, 386 detailRes.getRootPath(), 387 cms.getSitePath(page), 388 typeName); 389 String pageFolder = CmsFileUtil.removeTrailingSeparator(CmsResource.getParentFolder(page.getRootPath())); 390 CmsDetailPageFilter detailPageFilter = new CmsDetailPageFilter(cms, detailRes); 391 boolean foundDetailPage = detailPageFilter.filterDetailPages(context.getDetailPages()).anyMatch( 392 info -> pageFolder.equals(CmsFileUtil.removeTrailingSeparator(info.getUri()))); 393 CmsADEConfigData configForPage = context.getConfigForDetailPages(); 394 if (configForPage == null) { 395 LOG.debug(p + "Returned false because no valid sitemap configuration found"); 396 return false; 397 } 398 if (!foundDetailPage) { 399 LOG.debug(p + "Returned false because detail page is not in context " + configForPage.getBasePath()); 400 return false; 401 } 402 if (!CmsStringUtil.isPrefixPath( 403 configForPage.getExternalDetailContentExclusionFolder(), 404 detailRes.getRootPath())) { 405 LOG.debug( 406 p 407 + "returned false because of external detail content exclusion folder " 408 + configForPage.getExternalDetailContentExclusionFolder()); 409 return false; 410 } 411 return true; 412 } 413 414 /** 415 * Gets the context. 416 * 417 * @param manager the manager 418 * @param cms the cms 419 * @param contentPath the content path 420 * @param originPath the origin path 421 * @param resType the res type 422 * @return the context 423 */ 424 private DetailPageConfigData lookupDetailPageConfigData( 425 CmsADEManager manager, 426 CmsObject cms, 427 String contentPath, 428 String originPath, 429 String resType) { 430 431 DetailPageConfigData context = new DetailPageConfigData(); 432 433 CmsADEConfigData configData = manager.lookupConfigurationWithCache( 434 cms, 435 cms.getRequestContext().addSiteRoot(originPath)); 436 context.setSourceConfig(configData); 437 CmsADEConfigData targetConfigData = manager.lookupConfigurationWithCache(cms, contentPath); 438 context.setTargetConfig(targetConfigData); 439 boolean targetFirst = targetConfigData.isPreferDetailPagesForLocalContents(); 440 List<CmsADEConfigData> configs = targetFirst 441 ? Arrays.asList(targetConfigData, configData) 442 : Arrays.asList(configData, targetConfigData); 443 for (CmsADEConfigData config : configs) { 444 List<CmsDetailPageInfo> pageInfo = config.getDetailPagesForType(resType); 445 if ((pageInfo != null) && !pageInfo.isEmpty()) { 446 context.setConfigForDetailPages(config); 447 context.setDetailPages(pageInfo); 448 break; 449 } 450 } 451 return context; 452 } 453 454}