001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.staticexport; 029 030import org.opencms.ade.detailpage.CmsDetailPageUtil; 031import org.opencms.ade.detailpage.I_CmsDetailPageHandler; 032import org.opencms.db.CmsExportPoint; 033import org.opencms.file.CmsFile; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsProperty; 036import org.opencms.file.CmsPropertyDefinition; 037import org.opencms.file.CmsResource; 038import org.opencms.file.CmsVfsResourceNotFoundException; 039import org.opencms.file.types.CmsResourceTypeJsp; 040import org.opencms.i18n.CmsAcceptLanguageHeaderParser; 041import org.opencms.i18n.CmsI18nInfo; 042import org.opencms.i18n.CmsLocaleManager; 043import org.opencms.loader.CmsDumpLoader; 044import org.opencms.loader.CmsImageLoader; 045import org.opencms.loader.I_CmsResourceLoader; 046import org.opencms.loader.I_CmsStaticExportDirectResponseLoader; 047import org.opencms.loader.I_CmsStaticExportStreamLoader; 048import org.opencms.loader.I_CmsStoredContentDirectDeliveryLoader; 049import org.opencms.main.CmsContextInfo; 050import org.opencms.main.CmsEvent; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsIllegalArgumentException; 053import org.opencms.main.CmsLog; 054import org.opencms.main.CmsSystemInfo; 055import org.opencms.main.I_CmsEventListener; 056import org.opencms.main.OpenCms; 057import org.opencms.monitor.CmsMemoryMonitor; 058import org.opencms.report.CmsLogReport; 059import org.opencms.report.I_CmsReport; 060import org.opencms.security.CmsSecurityException; 061import org.opencms.site.CmsSite; 062import org.opencms.site.CmsSiteManagerImpl; 063import org.opencms.site.xmlsitemap.CmsXmlSeoConfiguration; 064import org.opencms.staticexport.CmsExportname.CmsExportNameComparator; 065import org.opencms.util.CmsFileUtil; 066import org.opencms.util.CmsMacroResolver; 067import org.opencms.util.CmsRequestUtil; 068import org.opencms.util.CmsStringUtil; 069import org.opencms.util.CmsUUID; 070import org.opencms.workplace.CmsWorkplace; 071 072import java.io.File; 073import java.io.FileOutputStream; 074import java.io.IOException; 075import java.net.MalformedURLException; 076import java.net.URL; 077import java.util.ArrayList; 078import java.util.Collections; 079import java.util.HashMap; 080import java.util.HashSet; 081import java.util.Iterator; 082import java.util.LinkedHashMap; 083import java.util.List; 084import java.util.Locale; 085import java.util.Map; 086import java.util.Map.Entry; 087import java.util.Set; 088import java.util.TreeMap; 089import java.util.regex.Pattern; 090 091import org.apache.commons.lang3.Strings; 092import org.apache.commons.logging.Log; 093 094import jakarta.servlet.RequestDispatcher; 095import jakarta.servlet.ServletException; 096import jakarta.servlet.http.HttpServletRequest; 097import jakarta.servlet.http.HttpServletResponse; 098 099/** 100 * Provides the functionality to export resources from the OpenCms VFS 101 * to the file system.<p> 102 * 103 * @since 6.0.0 104 */ 105public class CmsStaticExportManager implements I_CmsEventListener { 106 107 /** Name for the default file. */ 108 public static final String DEFAULT_FILE = "index.html"; 109 110 /** Marker for error message attribute. */ 111 public static final String EXPORT_ATTRIBUTE_ERROR_MESSAGE = RequestDispatcher.ERROR_MESSAGE; 112 113 /** Marker for error request uri attribute. */ 114 public static final String EXPORT_ATTRIBUTE_ERROR_REQUEST_URI = RequestDispatcher.ERROR_REQUEST_URI; 115 116 /** Marker for error servlet name attribute. */ 117 public static final String EXPORT_ATTRIBUTE_ERROR_SERVLET_NAME = RequestDispatcher.ERROR_SERVLET_NAME; 118 119 /** Marker for error status code attribute. */ 120 public static final String EXPORT_ATTRIBUTE_ERROR_STATUS_CODE = RequestDispatcher.ERROR_STATUS_CODE; 121 122 /** Name for the backup folder default name. */ 123 public static final String EXPORT_BACKUP_FOLDER_NAME = "backup"; 124 125 /** Name for the default work path. */ 126 public static final Integer EXPORT_DEFAULT_BACKUPS = Integer.valueOf(0); 127 128 /** Name for the folder default index file. */ 129 public static final String EXPORT_DEFAULT_FILE = "index_export.html"; 130 131 /** Name for the default work path. */ 132 public static final String EXPORT_DEFAULT_WORKPATH = CmsSystemInfo.FOLDER_WEBINF + "temp"; 133 134 /** Flag value for links without parameters. */ 135 public static final int EXPORT_LINK_WITH_PARAMETER = 2; 136 137 /** Flag value for links without parameters. */ 138 public static final int EXPORT_LINK_WITHOUT_PARAMETER = 1; 139 140 /** Marker for externally redirected 404 uri's. */ 141 public static final String EXPORT_MARKER = "exporturi"; 142 143 /** Time given (in seconds) to the static export handler to finish a publish task. */ 144 public static final int HANDLER_FINISH_TIME = 60; 145 146 /** Pattern to find '..' as a path component. */ 147 public static final Pattern PATTERN_PARENT_PATH_COMPONENT = Pattern.compile("(^|[/\\\\])\\.\\.([/\\\\]|$)"); 148 149 /** 150 * If the property 'secure' is set to this value, 151 * the resource will be delivered through http and https depending on the link source. 152 */ 153 public static final String SECURE_PROPERTY_VALUE_BOTH = "both"; 154 155 /** Cache value to indicate a true 404 error. */ 156 private static final String CACHEVALUE_404 = "?404"; 157 158 /** The log object for this class. */ 159 private static final Log LOG = CmsLog.getLog(CmsStaticExportManager.class); 160 161 /** HTTP header Accept-Charset. */ 162 private String m_acceptCharsetHeader; 163 164 /** HTTP header Accept-Language. */ 165 private String m_acceptLanguageHeader; 166 167 /** CMS context with admin permissions. */ 168 private CmsObject m_adminCms; 169 170 /** Cache for the export links. */ 171 private Map<String, Boolean> m_cacheExportLinks; 172 173 /** Cache for the export uris. */ 174 private Map<String, CmsStaticExportData> m_cacheExportUris; 175 176 /** Cache for the online links. */ 177 private Map<String, String> m_cacheOnlineLinks; 178 179 /** Cache for the secure links. */ 180 private Map<String, String> m_cacheSecureLinks; 181 182 /** OpenCms default charset header. */ 183 private String m_defaultAcceptCharsetHeader; 184 185 /** OpenCms default locale header. */ 186 private String m_defaultAcceptLanguageHeader; 187 188 /** Matcher for selecting those resources which should be part of the static export. */ 189 private CmsExportFolderMatcher m_exportFolderMatcher; 190 191 /** List of export resources which should be part of the static export. */ 192 private List<String> m_exportFolders; 193 194 /** The additional http headers for the static export. */ 195 private List<String> m_exportHeaders; 196 197 /** List of all resources that have the "exportname" property set: <system-wide unique export name, root path>. */ 198 private Map<CmsExportname, String> m_exportnameResources; 199 200 /** Indicates if <code>true</code> is the default value for the property "export". */ 201 private boolean m_exportPropertyDefault; 202 203 /** Indicates if links in the static export should be relative. */ 204 private boolean m_exportRelativeLinks; 205 206 /** List of export rules. */ 207 private List<CmsStaticExportExportRule> m_exportRules; 208 209 /** List of export suffixes where the "export" property default is always <code>true</code>. */ 210 private List<String> m_exportSuffixes; 211 212 /** Temporary variable for reading the xml config file. */ 213 private CmsStaticExportExportRule m_exportTmpRule; 214 215 /** Export url to send internal requests to. */ 216 private String m_exportUrl; 217 218 /** Export url with unsubstituted context values. */ 219 private String m_exportUrlConfigured; 220 221 /** Export url to send internal requests to without http://servername. */ 222 private String m_exportUrlPrefix; 223 224 /** Boolean value if the export is a full static export. */ 225 private boolean m_fullStaticExport; 226 227 /** Handler class for static export. */ 228 private I_CmsStaticExportHandler m_handler; 229 230 /** The configured link substitution handler. */ 231 private I_CmsLinkSubstitutionHandler m_linkSubstitutionHandler; 232 233 /** Lock object for write access to the {@link #cmsEvent(CmsEvent)} method. */ 234 private Object m_lockCmsEvent; 235 236 /** Lock object for export folder deletion in {@link #scrubExportFolders(I_CmsReport)}. */ 237 private Object m_lockScrubExportFolders; 238 239 /** Lock object for write access to the {@link #m_exportnameResources} map in {@link #computeVfsExportnames()}. */ 240 private Object m_lockSetExportnames; 241 242 /** The protected export path. */ 243 private String m_protectedExportPath; 244 245 /** The protected export points. */ 246 private Map<String, String> m_protectedExportPoints = new LinkedHashMap<String, String>(); 247 248 /** Indicates if the quick static export for plain resources is enabled. */ 249 private boolean m_quickPlainExport; 250 251 /** Remote address. */ 252 private String m_remoteAddr; 253 254 /** Prefix to use for exported files. */ 255 private String m_rfsPrefix; 256 257 /** Prefix to use for exported files with unsubstituted context values. */ 258 private String m_rfsPrefixConfigured; 259 260 /** List of configured rfs rules. */ 261 private List<CmsStaticExportRfsRule> m_rfsRules; 262 263 /** Temporary variable for reading the xml config file. */ 264 private CmsStaticExportRfsRule m_rfsTmpRule; 265 266 /** The number of backups stored for the export folder. */ 267 private Integer m_staticExportBackups; 268 269 /** Indicates if the static export is enabled or disabled. */ 270 private boolean m_staticExportEnabled; 271 272 /** The shared cache configuration. */ 273 private CmsSharedCacheConfiguration m_sharedCacheConfiguration = new CmsSharedCacheConfiguration(); 274 275 /** The path to where the static export will be written. */ 276 private String m_staticExportPath; 277 278 /** The path to where the static export will be written without the complete rfs path. */ 279 private String m_staticExportPathConfigured; 280 281 /** The path to where the static export will be written during the static export process. */ 282 private String m_staticExportWorkPath; 283 284 /** The path to where the static export will be written during the static export process without the complete rfs path. */ 285 private String m_staticExportWorkPathConfigured; 286 287 /** The stored content delivery configuration. */ 288 private CmsStoredContentDeliveryConfiguration m_storedContentDeliveryConfiguration = new CmsStoredContentDeliveryConfiguration(); 289 290 /** Support for stored content delivery during static export. */ 291 private CmsStoredContentDeliverySupport m_storedContentDeliverySupport = new CmsStoredContentDeliverySupport(); 292 293 /** Vfs Name of a resource used to do a "static export required" test. */ 294 private String m_testResource; 295 296 /** If there are several identical export paths the usage of temporary directories has to be disabled. */ 297 private boolean m_useTempDirs = true; 298 299 /** Prefix to use for internal OpenCms files. */ 300 private String m_vfsPrefix; 301 302 /** Prefix to use for internal OpenCms files with unsubstituted context values. */ 303 private String m_vfsPrefixConfigured; 304 305 /** 306 * Creates a new static export property object.<p> 307 * 308 */ 309 public CmsStaticExportManager() { 310 311 m_lockCmsEvent = new Object(); 312 m_lockScrubExportFolders = new Object(); 313 m_lockSetExportnames = new Object(); 314 m_exportSuffixes = new ArrayList<String>(); 315 m_exportFolders = new ArrayList<String>(); 316 m_exportHeaders = new ArrayList<String>(); 317 m_rfsRules = new ArrayList<CmsStaticExportRfsRule>(); 318 m_exportRules = new ArrayList<CmsStaticExportExportRule>(); 319 m_exportTmpRule = new CmsStaticExportExportRule("", ""); 320 m_rfsTmpRule = new CmsStaticExportRfsRule("", "", "", "", "", "", null, null); 321 m_fullStaticExport = false; 322 } 323 324 /** 325 * Creates unique, valid RFS name for the given filename that contains 326 * a coded version of the given parameters, with the given file extension appended.<p> 327 * 328 * Adapted from CmsFileUtil.getRfsPath(). 329 * 330 * @param filename the base file name 331 * @param extension the extension to use 332 * @param parameters the parameters to code in the result file name 333 * 334 * @return a unique, valid RFS name for the given parameters 335 * 336 * @see org.opencms.staticexport.CmsStaticExportManager 337 */ 338 public static String getRfsPath(String filename, String extension, String parameters) { 339 340 boolean appendSlash = false; 341 if (filename.endsWith("/")) { 342 appendSlash = true; 343 filename = filename.substring(0, filename.length() - 1); 344 } 345 StringBuffer buf = new StringBuffer(128); 346 buf.append(filename); 347 buf.append('_'); 348 int h = parameters.hashCode(); 349 // ensure we do have a positive id value 350 buf.append(h > 0 ? h : -h); 351 buf.append(extension); 352 if (appendSlash) { 353 buf.append("/"); 354 } 355 return buf.toString(); 356 } 357 358 /** 359 * Returns the real file system name plus the default file name.<p> 360 * 361 * @param rfsName the real file system name to append the default file name to 362 * @param isFolder signals whether the according virtual file system resource is an folder or not 363 * 364 * @return the real file system name plus the default file name 365 */ 366 public String addDefaultFileNameToFolder(String rfsName, boolean isFolder) { 367 368 StringBuffer name = new StringBuffer(rfsName); 369 370 if (isFolder) { 371 // vfs folder case 372 name.append(EXPORT_DEFAULT_FILE); 373 } 374 return name.toString(); 375 } 376 377 /** 378 * Adds a new export rule to the configuration.<p> 379 * 380 * @param name the name of the rule 381 * @param description the description for the rule 382 */ 383 public void addExportRule(String name, String description) { 384 385 m_exportRules.add( 386 new CmsStaticExportExportRule( 387 name, 388 description, 389 m_exportTmpRule.getModifiedResources(), 390 m_exportTmpRule.getExportResourcePatterns())); 391 m_exportTmpRule = new CmsStaticExportExportRule("", ""); 392 } 393 394 /** 395 * Adds a regex to the latest export rule.<p> 396 * 397 * @param regex the regex to add 398 */ 399 public void addExportRuleRegex(String regex) { 400 401 m_exportTmpRule.addModifiedResource(regex); 402 } 403 404 /** 405 * Adds a export uri to the latest export rule.<p> 406 * 407 * @param exportUri the export uri to add 408 */ 409 public void addExportRuleUri(String exportUri) { 410 411 m_exportTmpRule.addExportResourcePattern(exportUri); 412 } 413 414 /** 415 * Adds an protected export point.<p> 416 * 417 * @param uri the source URI 418 * @param destination the export destination 419 */ 420 public void addProtectedExportPoint(String uri, String destination) { 421 422 m_protectedExportPoints.put(uri, destination); 423 } 424 425 /** 426 * Adds a new rfs rule to the configuration.<p> 427 * 428 * @param name the name of the rule 429 * @param description the description for the rule 430 * @param source the source regex 431 * @param rfsPrefix the url prefix 432 * @param exportPath the rfs export path 433 * @param exportWorkPath the rfs export work path 434 * @param exportBackups the number of backups 435 * @param useRelativeLinks the relative links value 436 */ 437 public void addRfsRule( 438 String name, 439 String description, 440 String source, 441 String rfsPrefix, 442 String exportPath, 443 String exportWorkPath, 444 String exportBackups, 445 String useRelativeLinks) { 446 447 if ((m_staticExportPathConfigured != null) && exportPath.equals(m_staticExportPathConfigured)) { 448 m_useTempDirs = false; 449 } 450 Iterator<CmsStaticExportRfsRule> itRules = m_rfsRules.iterator(); 451 while (m_useTempDirs && itRules.hasNext()) { 452 CmsStaticExportRfsRule rule = itRules.next(); 453 if (exportPath.equals(rule.getExportPathConfigured())) { 454 m_useTempDirs = false; 455 } 456 } 457 Boolean relativeLinks = (useRelativeLinks == null ? null : Boolean.valueOf(useRelativeLinks)); 458 Integer backups = (exportBackups == null ? null : Integer.valueOf(exportBackups)); 459 460 m_rfsRules.add( 461 new CmsStaticExportRfsRule( 462 name, 463 description, 464 source, 465 rfsPrefix, 466 exportPath, 467 exportWorkPath, 468 backups, 469 relativeLinks, 470 m_rfsTmpRule.getRelatedSystemResources())); 471 m_rfsTmpRule = new CmsStaticExportRfsRule("", "", "", "", "", "", null, null); 472 } 473 474 /** 475 * Adds a regex of related system resources to the latest rfs-rule.<p> 476 * 477 * @param regex the regex to add 478 */ 479 public void addRfsRuleSystemRes(String regex) { 480 481 m_rfsTmpRule.addRelatedSystemRes(regex); 482 } 483 484 /** 485 * Caches a calculated online link.<p> 486 * 487 * @param linkName the link 488 * @param vfsName the name of the VFS resource 489 */ 490 public void cacheOnlineLink(String linkName, String vfsName) { 491 492 m_cacheOnlineLinks.put(linkName, vfsName); 493 } 494 495 /** 496 * Implements the CmsEvent interface, 497 * the static export properties uses the events to clear 498 * the list of cached keys in case a project is published.<p> 499 * 500 * @param event CmsEvent that has occurred 501 */ 502 public void cmsEvent(CmsEvent event) { 503 504 if (!isStaticExportEnabled()) { 505 if (LOG.isWarnEnabled()) { 506 LOG.warn(Messages.get().getBundle().key(Messages.LOG_STATIC_EXPORT_DISABLED_0)); 507 } 508 // we still need to clear all caches 509 switch (event.getType()) { 510 case I_CmsEventListener.EVENT_UPDATE_EXPORTS: 511 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 512 case I_CmsEventListener.EVENT_CLEAR_CACHES: 513 clearCaches(event); 514 break; 515 default: 516 // no operation 517 } 518 return; 519 } 520 if (m_sharedCacheConfiguration.isEnabled()) { 521 switch (event.getType()) { 522 case I_CmsEventListener.EVENT_UPDATE_EXPORTS: 523 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 524 case I_CmsEventListener.EVENT_CLEAR_CACHES: 525 clearCaches(event); 526 break; 527 default: 528 // no operation 529 } 530 return; 531 } 532 I_CmsReport report = null; 533 Map<String, Object> data = event.getData(); 534 if (data != null) { 535 report = (I_CmsReport)data.get(I_CmsEventListener.KEY_REPORT); 536 } 537 if (report == null) { 538 report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass()); 539 } 540 switch (event.getType()) { 541 case I_CmsEventListener.EVENT_UPDATE_EXPORTS: 542 scrubExportFolders(report); 543 clearCaches(event); 544 break; 545 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 546 if (data == null) { 547 if (LOG.isErrorEnabled()) { 548 LOG.error(Messages.get().getBundle().key(Messages.ERR_EMPTY_EVENT_DATA_0)); 549 } 550 return; 551 } 552 // event data contains a list of the published resources 553 CmsUUID publishHistoryId = new CmsUUID((String)data.get(I_CmsEventListener.KEY_PUBLISHID)); 554 if (LOG.isDebugEnabled()) { 555 LOG.debug(Messages.get().getBundle().key(Messages.LOG_EVENT_PUBLISH_PROJECT_1, publishHistoryId)); 556 } 557 synchronized (m_lockCmsEvent) { 558 getHandler().performEventPublishProject(publishHistoryId, report); 559 } 560 clearCaches(event); 561 562 if (LOG.isDebugEnabled()) { 563 LOG.debug( 564 Messages.get().getBundle().key( 565 Messages.LOG_EVENT_PUBLISH_PROJECT_FINISHED_1, 566 publishHistoryId)); 567 } 568 569 break; 570 case I_CmsEventListener.EVENT_CLEAR_CACHES: 571 clearCaches(event); 572 break; 573 default: 574 // no operation 575 } 576 } 577 578 /** 579 * Exports the requested uri and at the same time writes the uri to the response output stream 580 * if required.<p> 581 * 582 * @param req the current request 583 * @param res the current response 584 * @param cms an initialised cms context (should be initialised with the "Guest" user only) 585 * @param data the static export data set 586 * 587 * @return status code of the export operation, status codes are the same as http status codes (200,303,304) 588 * 589 * @throws CmsException in case of errors accessing the VFS 590 * @throws ServletException in case of errors accessing the servlet 591 * @throws IOException in case of errors writing to the export output stream 592 * @throws CmsStaticExportException if static export is disabled 593 */ 594 public int export(HttpServletRequest req, HttpServletResponse res, CmsObject cms, CmsStaticExportData data) 595 throws CmsException, IOException, ServletException, CmsStaticExportException { 596 597 if (m_sharedCacheConfiguration.isEnabled() && ((req == null) || (res == null))) { 598 throw new CmsStaticExportException(Messages.get().container(Messages.ERR_SHAREDCACHE_ON_DEMAND_ONLY_0)); 599 } 600 601 CmsResource resource = data.getResource(); 602 String vfsName = data.getVfsName(); 603 String rfsName; 604 if (data.isDetailPage()) { 605 rfsName = CmsStringUtil.joinPaths(data.getRfsName(), CmsStaticExportManager.DEFAULT_FILE); 606 } else if (data.getParameters() != null) { 607 rfsName = data.getRfsName(); 608 } else { 609 rfsName = addDefaultFileNameToFolder(data.getRfsName(), resource.isFolder()); 610 } 611 612 // cut the site root from the vfsName and switch to the correct site 613 String siteRoot = OpenCms.getSiteManager().getSiteRoot(vfsName); 614 615 CmsI18nInfo i18nInfo = OpenCms.getLocaleManager().getI18nInfo( 616 req, 617 cms.getRequestContext().getCurrentUser(), 618 cms.getRequestContext().getCurrentProject(), 619 vfsName); 620 621 String remoteAddr = m_remoteAddr; 622 if (remoteAddr == null) { 623 remoteAddr = CmsContextInfo.LOCALHOST; 624 } 625 626 if (siteRoot != null) { 627 vfsName = vfsName.substring(siteRoot.length()); 628 } else { 629 siteRoot = "/"; 630 } 631 632 if (LOG.isDebugEnabled()) { 633 LOG.debug(Messages.get().getBundle().key(Messages.LOG_STATIC_EXPORT_SITE_ROOT_2, siteRoot, vfsName)); 634 } 635 636 boolean usesSecureSite = (req != null) && OpenCms.getSiteManager().usesSecureSite(req); 637 CmsContextInfo contextInfo = new CmsContextInfo( 638 cms.getRequestContext().getCurrentUser(), 639 cms.getRequestContext().getCurrentProject(), 640 vfsName, 641 cms.getRequestContext().getRequestMatcher(), 642 siteRoot, 643 usesSecureSite, 644 i18nInfo.getLocale(), 645 i18nInfo.getEncoding(), 646 remoteAddr, 647 CmsContextInfo.CURRENT_TIME, 648 cms.getRequestContext().getOuFqn(), 649 cms.getRequestContext().isForceAbsoluteLinks()); 650 CmsObject exportCms = OpenCms.initCmsObject(null, contextInfo); 651 652 // only export those resources where the export property is set 653 if (!isExportLink(exportCms, exportCms.getRequestContext().removeSiteRoot(data.getVfsName()))) { 654 // the resource was not used for export, so return HttpServletResponse.SC_SEE_OTHER 655 // as a signal for not exported resource 656 return HttpServletResponse.SC_SEE_OTHER; 657 } 658 659 // this flag signals if the export method is used for "on demand" or "after publish". 660 // if no request and result stream are available, it was called during "export on publish" 661 boolean exportOnDemand = ((req != null) && (res != null)); 662 HttpServletRequest exportRequest = req; 663 if (exportOnDemand && (data.getParameters() != null)) { 664 exportRequest = new CmsStaticExportRequest(req, data); 665 } 666 CmsStaticExportResponseWrapper wrapRes = null; 667 if (res != null) { 668 wrapRes = new CmsStaticExportResponseWrapper(res); 669 } 670 boolean exportWithResponse = true; 671 if (LOG.isDebugEnabled()) { 672 LOG.debug(Messages.get().getBundle().key(Messages.LOG_SE_RESOURCE_START_1, data)); 673 } 674 675 CmsResource exportResource = OpenCms.initResource(exportCms, vfsName, exportRequest, wrapRes); 676 I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(exportResource); 677 CmsFile file = null; 678 if ((loader == null) || !(loader instanceof I_CmsStaticExportStreamLoader)) { 679 file = exportCms.readFile(exportResource); 680 exportResource = file; 681 } 682 vfsName = exportCms.getSitePath(exportResource); 683 684 // check loader id for resource 685 if ((loader == null) || (!loader.isStaticExportEnabled())) { 686 Object[] arguments = new Object[] {vfsName, Integer.valueOf(exportResource.getTypeId())}; 687 throw new CmsStaticExportException( 688 Messages.get().container(Messages.ERR_EXPORT_NOT_SUPPORTED_2, arguments)); 689 } 690 691 // ensure we have exactly the same setup as if called "the usual way" 692 // we only have to do this in case of the static export on demand 693 String mimetype = null; 694 if (exportOnDemand) { 695 mimetype = OpenCms.getResourceManager().getMimeType( 696 exportResource.getName(), 697 exportCms.getRequestContext().getEncoding()); 698 if (wrapRes != null) { 699 wrapRes.setContentType(mimetype); 700 } 701 exportCms.getRequestContext().setUri(vfsName); 702 } 703 704 if (m_sharedCacheConfiguration.isEnabled()) { 705 return exportToSharedCache(loader, exportCms, exportResource, file, exportRequest, wrapRes, mimetype); 706 } 707 708 // do the export 709 int status = -1; 710 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(exportCms, vfsName); 711 boolean exported = false; 712 boolean matched = false; 713 // iterate over all rules 714 Iterator<CmsStaticExportRfsRule> it = getRfsRules().iterator(); 715 while (it.hasNext()) { 716 CmsStaticExportRfsRule rule = it.next(); 717 // normal case 718 719 boolean export = rule.getSource().matcher(CmsStringUtil.joinPaths(siteRoot, vfsName)).matches(); 720 matched |= export; 721 // system folder case 722 export |= ((OpenCms.getSiteManager().startsWithShared(vfsName) 723 || vfsName.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) && rule.match(vfsName)); 724 if (export) { 725 // the resource has to exported for this rule 726 CmsObject locCms = exportCms; 727 Locale locale = CmsLocaleManager.getLocale(rule.getName()); 728 if (locales.contains(locale)) { 729 // if the locale is in the default locales for the resource 730 // so adjust the locale to use for exporting 731 CmsContextInfo ctxInfo = new CmsContextInfo(exportCms.getRequestContext()); 732 ctxInfo.setLocale(locale); 733 locCms = OpenCms.initCmsObject(exportCms, ctxInfo); 734 } 735 String locRfsName = rfsName; 736 // in case of the default locale, this would either be wrong or the identity substitution 737 if (!locale.equals(CmsLocaleManager.getDefaultLocale()) && locales.contains(locale)) { 738 locRfsName = rule.getLocalizedRfsName(rfsName, "/"); 739 } 740 if (loader instanceof I_CmsStaticExportStreamLoader) { 741 writeStreamedResource( 742 (I_CmsStaticExportStreamLoader)loader, 743 locCms, 744 exportResource, 745 exportRequest, 746 exportWithResponse ? wrapRes : null, 747 rule.getExportPath(), 748 locRfsName, 749 resource); 750 // disable writing to response for static resources after the first rule match 751 // to avoid duplicate response data 752 // when compression is enabled in Tomcat. 753 exportWithResponse = false; 754 exported = true; 755 } else { 756 // read the content in the matching locale 757 byte[] content = loader.export( 758 locCms, 759 new CmsFile(file), 760 exportRequest, 761 exportWithResponse ? wrapRes : null); 762 if (content != null) { 763 // write to rfs 764 exported = true; 765 writeResource(req, rule.getExportPath(), locRfsName, resource, content); 766 } 767 } 768 } 769 } 770 if (!matched) { 771 // no rule matched 772 String exportPath = getExportPath(siteRoot + vfsName); 773 if (loader instanceof I_CmsStaticExportStreamLoader) { 774 writeStreamedResource( 775 (I_CmsStaticExportStreamLoader)loader, 776 exportCms, 777 exportResource, 778 exportRequest, 779 exportWithResponse ? wrapRes : null, 780 exportPath, 781 rfsName, 782 resource); 783 exported = true; 784 } else { 785 byte[] content = loader.export( 786 exportCms, 787 new CmsFile(file), 788 exportRequest, 789 exportWithResponse ? wrapRes : null); 790 if (content != null) { 791 exported = true; 792 writeResource(req, exportPath, rfsName, resource, content); 793 } 794 } 795 } 796 797 if (exported) { 798 // get the wrapper status that was set 799 status = (wrapRes != null) ? wrapRes.getStatus() : -1; 800 if (status < 0) { 801 // the status was not set, assume everything is o.k. 802 status = HttpServletResponse.SC_OK; 803 } 804 } else { 805 // the resource was not written because it was not modified. 806 // set the status to not modified 807 status = HttpServletResponse.SC_NOT_MODIFIED; 808 } 809 810 return status; 811 } 812 813 /** 814 * Starts a complete static export of all resources.<p> 815 * 816 * @param purgeFirst flag to delete all resources in the export folder of the rfs 817 * @param report an I_CmsReport instance to print output message, or null to write messages to the log file 818 * 819 * @throws CmsException in case of errors accessing the VFS 820 * @throws IOException in case of errors writing to the export output stream 821 * @throws ServletException in case of errors accessing the servlet 822 */ 823 public synchronized void exportFullStaticRender(boolean purgeFirst, I_CmsReport report) 824 throws CmsException, IOException, ServletException { 825 826 if (m_sharedCacheConfiguration.isEnabled()) { 827 throw new CmsStaticExportException(Messages.get().container(Messages.ERR_SHAREDCACHE_FULL_EXPORT_0)); 828 } 829 830 // set member to true to get temporary export paths for rules 831 m_fullStaticExport = true; 832 // save the real export path 833 String staticExportPathStore = m_staticExportPath; 834 835 if (m_useTempDirs) { 836 // set the export path to the export work path 837 m_staticExportPath = m_staticExportWorkPath; 838 } 839 840 // delete all old exports if the purgeFirst flag is set 841 if (purgeFirst) { 842 Map<String, Object> eventData = new HashMap<String, Object>(); 843 eventData.put(I_CmsEventListener.KEY_REPORT, report); 844 CmsEvent clearCacheEvent = new CmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, eventData); 845 OpenCms.fireCmsEvent(clearCacheEvent); 846 847 scrubExportFolders(report); 848 // this will always use the root site 849 CmsObject cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); 850 cms.deleteAllStaticExportPublishedResources(EXPORT_LINK_WITHOUT_PARAMETER); 851 cms.deleteAllStaticExportPublishedResources(EXPORT_LINK_WITH_PARAMETER); 852 } 853 854 // do the export 855 CmsAfterPublishStaticExportHandler handler = new CmsAfterPublishStaticExportHandler(); 856 // export everything 857 handler.doExportAfterPublish(null, report); 858 859 // set export path to the original one 860 m_staticExportPath = staticExportPathStore; 861 862 // set member to false for further exports 863 m_fullStaticExport = false; 864 865 // check if report contents no errors 866 if (m_useTempDirs && !report.hasError()) { 867 // backup old export folders for default export 868 File staticExport = new File(m_staticExportPath); 869 createExportBackupFolders(staticExport, m_staticExportPath, getExportBackups().intValue(), null); 870 871 // change the name of the used temporary export folder to the original default export path 872 File staticExportWork = new File(m_staticExportWorkPath); 873 staticExportWork.renameTo(new File(m_staticExportPath)); 874 875 // backup old export folders of rule based exports 876 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 877 while (it.hasNext()) { 878 CmsStaticExportRfsRule rule = it.next(); 879 File staticExportRule = new File(rule.getExportPath()); 880 File staticExportWorkRule = new File(rule.getExportWorkPath()); 881 // only backup if a temporary folder exists for this rule 882 if (staticExportWorkRule.exists()) { 883 createExportBackupFolders( 884 staticExportRule, 885 rule.getExportPath(), 886 rule.getExportBackups().intValue(), 887 OpenCms.getResourceManager().getFileTranslator().translateResource(rule.getName())); 888 staticExportWorkRule.renameTo(new File(rule.getExportPath())); 889 } 890 } 891 } else if (report.hasError()) { 892 report.println(Messages.get().container(Messages.ERR_EXPORT_NOT_SUCCESSFUL_0), I_CmsReport.FORMAT_WARNING); 893 } 894 } 895 896 /** 897 * Returns the accept-charset header used for internal requests.<p> 898 * 899 * @return the accept-charset header 900 */ 901 public String getAcceptCharsetHeader() { 902 903 return m_acceptCharsetHeader; 904 } 905 906 /** 907 * Returns the accept-language header used for internal requests.<p> 908 * 909 * @return the accept-language header 910 */ 911 public String getAcceptLanguageHeader() { 912 913 return m_acceptLanguageHeader; 914 } 915 916 /** 917 * Returns a cached link for the given vfs name.<p> 918 * 919 * @param vfsName the name of the vfs resource to get the cached link for 920 * 921 * @return a cached link for the given vfs name, or null 922 */ 923 public String getCachedOnlineLink(String vfsName) { 924 925 return m_cacheOnlineLinks.get(vfsName); 926 } 927 928 /** 929 * Returns the key for the online, export and secure cache.<p> 930 * 931 * @param siteRoot the site root of the resource 932 * @param uri the URI of the resource 933 * 934 * @return a key for the cache 935 */ 936 public String getCacheKey(String siteRoot, String uri) { 937 938 return new StringBuffer(siteRoot).append(uri).toString(); 939 } 940 941 /** 942 * Gets the default property value as a string representation.<p> 943 * 944 * @return <code>"true"</code> or <code>"false"</code> 945 */ 946 public String getDefault() { 947 948 return String.valueOf(m_exportPropertyDefault); 949 } 950 951 /** 952 * Returns the current default charset header.<p> 953 * 954 * @return the current default charset header 955 */ 956 public String getDefaultAcceptCharsetHeader() { 957 958 return m_defaultAcceptCharsetHeader; 959 } 960 961 /** 962 * Returns the current default locale header.<p> 963 * 964 * @return the current default locale header 965 */ 966 public String getDefaultAcceptLanguageHeader() { 967 968 return m_defaultAcceptLanguageHeader; 969 } 970 971 /** 972 * Returns the default prefix for exported links in the "real" file system.<p> 973 * 974 * @return the default prefix for exported links in the "real" file system 975 */ 976 public String getDefaultRfsPrefix() { 977 978 return m_rfsPrefix; 979 } 980 981 /** 982 * Returns the number of stored backups.<p> 983 * 984 * @return the number of stored backups 985 */ 986 public Integer getExportBackups() { 987 988 if (m_staticExportBackups != null) { 989 return m_staticExportBackups; 990 } 991 // if backups not configured set to default value 992 return EXPORT_DEFAULT_BACKUPS; 993 } 994 995 /** 996 * Returns the export data for the request, if null is returned no export is required.<p> 997 * 998 * @param request the request to check for export data 999 * @param cms an initialized cms context (should be initialized with the "Guest" user only 1000 * 1001 * @return the export data for the request, if null is returned no export is required 1002 */ 1003 public CmsStaticExportData getExportData(HttpServletRequest request, CmsObject cms) { 1004 1005 if (!isStaticExportEnabled()) { 1006 // export is disabled 1007 return null; 1008 } 1009 1010 // build the rfs name for the export "on demand" 1011 String rfsName = request.getParameter(EXPORT_MARKER); 1012 if ((rfsName == null)) { 1013 rfsName = (String)request.getAttribute(EXPORT_ATTRIBUTE_ERROR_REQUEST_URI); 1014 } 1015 1016 if (request.getHeader(CmsRequestUtil.HEADER_OPENCMS_EXPORT) != null) { 1017 // this is a request created by the static export and directly send to 404 handler 1018 // so remove the leading handler identification 1019 int prefix = rfsName.startsWith(getExportUrlPrefix()) ? getExportUrlPrefix().length() : 0; 1020 if (prefix > 0) { 1021 rfsName = rfsName.substring(prefix); 1022 } else { 1023 return null; 1024 } 1025 } 1026 1027 if (!isValidRfsName(rfsName)) { 1028 // this is not an export request, no further processing is required 1029 return null; 1030 } 1031 1032 // store the site root 1033 String storedSiteRoot = cms.getRequestContext().getSiteRoot(); 1034 try { 1035 // get the site root according to the HttpServletRequest 1036 CmsSite site = OpenCms.getSiteManager().matchRequest(request); 1037 // set the site root of the request context before getting the export data 1038 cms.getRequestContext().setSiteRoot(site.getSiteRoot()); 1039 // get the export data now 1040 CmsStaticExportData data = getRfsExportData(cms, rfsName); 1041 1042 // check if we have an export link, 1043 // only return the data object if we really should export the resource 1044 if ((data != null) && isExportLink(cms, cms.getRequestContext().removeSiteRoot(data.getVfsName()))) { 1045 // if we have an export link return the export data object 1046 return data; 1047 } else { 1048 // otherwise if we have a link vfsName which should not be exported 1049 // return null for better error handling in the OpenCmsServlet 1050 return null; 1051 } 1052 } finally { 1053 // restore the site root 1054 cms.getRequestContext().setSiteRoot(storedSiteRoot); 1055 } 1056 } 1057 1058 /** 1059 * Gets the export enabled value as a string representation.<p> 1060 * 1061 * @return <code>"true"</code> or <code>"false"</code> 1062 */ 1063 public String getExportEnabled() { 1064 1065 return String.valueOf(m_staticExportEnabled); 1066 } 1067 1068 /** 1069 * Returns the current folder matcher.<p> 1070 * 1071 * @return the current folder matcher 1072 */ 1073 public CmsExportFolderMatcher getExportFolderMatcher() { 1074 1075 return m_exportFolderMatcher; 1076 } 1077 1078 /** 1079 * Returns list of resources patterns which are part of the export.<p> 1080 * 1081 * @return the of resources patterns which are part of the export. 1082 */ 1083 public List<String> getExportFolderPatterns() { 1084 1085 return Collections.unmodifiableList(m_exportFolders); 1086 } 1087 1088 /** 1089 * Returns specific http headers for the static export.<p> 1090 * 1091 * If the header <code>Cache-Control</code> is set, OpenCms will not use its default headers.<p> 1092 * 1093 * @return the list of http export headers 1094 */ 1095 public List<String> getExportHeaders() { 1096 1097 return Collections.unmodifiableList(m_exportHeaders); 1098 } 1099 1100 /** 1101 * Returns a map of all export names with export name as key 1102 * and the vfs folder path as value.<p> 1103 * 1104 * @return a map of export names 1105 */ 1106 public Map<CmsExportname, String> getExportnames() { 1107 1108 if (m_exportnameResources == null) { 1109 try { 1110 TreeMap<CmsExportname, String> sort = new TreeMap<CmsExportname, String>(new CmsExportNameComparator()); 1111 sort.putAll(computeVfsExportnames()); 1112 m_exportnameResources = sort; 1113 } catch (Throwable t) { 1114 LOG.error(t.getMessage(), t); 1115 } 1116 } 1117 if (LOG.isDebugEnabled()) { 1118 LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_FINISHED_0)); 1119 } 1120 return Collections.unmodifiableMap(m_exportnameResources); 1121 } 1122 1123 /** 1124 * Returns the export path for the static export, that is the folder where the 1125 * static exported resources will be written to.<p> 1126 * 1127 * The returned value will be a directory like prefix. The value is configured 1128 * in the <code>opencms-importexport.xml</code> configuration file. An optimization 1129 * of the configured value will be performed, where all relative path information is resolved 1130 * (for example <code>/export/../static</code> will be resolved to <code>/export</code>. 1131 * Moreover, if the configured path ends with a <code>/</code>, this will be cut off 1132 * (for example <code>/export/</code> becomes <code>/export</code>.<p> 1133 * 1134 * This is resource name based, and based on the rfs-rules defined in the 1135 * <code>opencms-importexport.xml</code> configuration file.<p> 1136 * 1137 * @param vfsName the name of the resource to export 1138 * 1139 * @return the export path for the static export, that is the folder where the 1140 * 1141 * @see #getRfsPrefix(String) 1142 * @see #getVfsPrefix() 1143 */ 1144 public String getExportPath(String vfsName) { 1145 1146 if (vfsName != null) { 1147 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 1148 while (it.hasNext()) { 1149 CmsStaticExportRfsRule rule = it.next(); 1150 if (rule.getSource().matcher(vfsName).matches()) { 1151 return rule.getExportPath(); 1152 } 1153 } 1154 } 1155 if (m_useTempDirs && isFullStaticExport()) { 1156 return getExportWorkPath(); 1157 } 1158 return m_staticExportPath; 1159 } 1160 1161 /** 1162 * Returns the original configured export path for the static export without the complete rfs path, to be used 1163 * when re-writing the configuration.<p> 1164 * 1165 * This is required <b>only</b> to serialize the configuration again exactly as it was configured. 1166 * This method should <b>not</b> be used otherwise. Use <code>{@link #getExportPath(String)}</code> 1167 * to obtain the export path to use when exporting.<p> 1168 * 1169 * @return the original configured export path for the static export without the complete rfs path 1170 */ 1171 public String getExportPathForConfiguration() { 1172 1173 return m_staticExportPathConfigured; 1174 } 1175 1176 /** 1177 * Returns the protected export points.<p> 1178 * 1179 * @return the protected export points 1180 */ 1181 public Set<CmsExportPoint> getExportPoints() { 1182 1183 Set<CmsExportPoint> result = new HashSet<CmsExportPoint>(); 1184 for (Entry<String, String> entry : m_protectedExportPoints.entrySet()) { 1185 result.add( 1186 new CmsExportPoint(entry.getKey(), CmsStringUtil.joinPaths(m_protectedExportPath, entry.getValue()))); 1187 } 1188 return result; 1189 } 1190 1191 /** 1192 * Returns true if the default value for the resource property "export" is true.<p> 1193 * 1194 * @return true if the default value for the resource property "export" is true 1195 */ 1196 public boolean getExportPropertyDefault() { 1197 1198 return m_exportPropertyDefault; 1199 } 1200 1201 /** 1202 * Returns the export Rules.<p> 1203 * 1204 * @return the export Rules 1205 */ 1206 public List<CmsStaticExportExportRule> getExportRules() { 1207 1208 return Collections.unmodifiableList(m_exportRules); 1209 } 1210 1211 /** 1212 * Gets the list of resource suffixes which will be exported by default.<p> 1213 * 1214 * @return list of resource suffixes 1215 */ 1216 public List<String> getExportSuffixes() { 1217 1218 return m_exportSuffixes; 1219 } 1220 1221 /** 1222 * Returns the export URL used for internal requests for exporting resources that require a 1223 * request / response (like JSP).<p> 1224 * 1225 * @return the export URL used for internal requests for exporting resources like JSP 1226 */ 1227 public String getExportUrl() { 1228 1229 return m_exportUrl; 1230 } 1231 1232 /** 1233 * Returns the export URL used for internal requests with unsubstituted context values, to be used 1234 * when re-writing the configuration.<p> 1235 * 1236 * This is required <b>only</b> to serialize the configuration again exactly as it was configured. 1237 * This method should <b>not</b> be used otherwise. Use <code>{@link #getExportUrl()}</code> 1238 * to obtain the export path to use when exporting.<p> 1239 * 1240 * @return the export URL used for internal requests with unsubstituted context values 1241 */ 1242 public String getExportUrlForConfiguration() { 1243 1244 return m_exportUrlConfigured; 1245 } 1246 1247 /** 1248 * Returns the export URL used for internal requests for exporting resources that require a 1249 * request / response (like JSP) without http://servername.<p> 1250 * 1251 * @return the export URL used for internal requests for exporting resources like JSP without http://servername 1252 */ 1253 public String getExportUrlPrefix() { 1254 1255 return m_exportUrlPrefix; 1256 } 1257 1258 /** 1259 * Returns the export work path for the static export, that is the folder where the 1260 * static exported resources will be written to during the export process.<p> 1261 * 1262 * @return the export work path for the static export 1263 */ 1264 public String getExportWorkPath() { 1265 1266 return m_staticExportWorkPath; 1267 } 1268 1269 /** 1270 * Returns the original configured export work path for the static export without the complete rfs path, to be used 1271 * when re-writing the configuration.<p> 1272 * 1273 * @return the original configured export work path for the static export without the complete rfs path 1274 */ 1275 public String getExportWorkPathForConfiguration() { 1276 1277 if (m_staticExportWorkPathConfigured != null) { 1278 return m_staticExportWorkPathConfigured; 1279 } 1280 // if work path not configured set to default value 1281 return EXPORT_DEFAULT_WORKPATH; 1282 } 1283 1284 /** 1285 * Returns the configured static export handler class.<p> 1286 * 1287 * If not set, a new <code>{@link CmsAfterPublishStaticExportHandler}</code> is created and returned.<p> 1288 * 1289 * @return the configured static export handler class 1290 */ 1291 public I_CmsStaticExportHandler getHandler() { 1292 1293 if (m_handler == null) { 1294 setHandler(CmsOnDemandStaticExportHandler.class.getName()); 1295 } 1296 return m_handler; 1297 } 1298 1299 /** 1300 * Returns the configured link substitution handler class.<p> 1301 * 1302 * If not set, a new <code>{@link CmsDefaultLinkSubstitutionHandler}</code> is created and returned.<p> 1303 * 1304 * @return the configured link substitution handler class 1305 */ 1306 public I_CmsLinkSubstitutionHandler getLinkSubstitutionHandler() { 1307 1308 if (m_linkSubstitutionHandler == null) { 1309 setLinkSubstitutionHandler(CmsDefaultLinkSubstitutionHandler.class.getName()); 1310 } 1311 return m_linkSubstitutionHandler; 1312 } 1313 1314 /** 1315 * Gets the plain export optimization value as a string representation.<p> 1316 * 1317 * @return <code>"true"</code> or <code>"false"</code> 1318 */ 1319 public String getPlainExportOptimization() { 1320 1321 return String.valueOf(m_quickPlainExport); 1322 } 1323 1324 /** 1325 * Returns the protected export name for the given root path.<p> 1326 * 1327 * @param rootPath the root path 1328 * 1329 * @return the protected export name 1330 */ 1331 public String getProtectedExportName(String rootPath) { 1332 1333 String result = null; 1334 for (Entry<String, String> entry : m_protectedExportPoints.entrySet()) { 1335 if (rootPath.startsWith(entry.getKey())) { 1336 result = CmsStringUtil.joinPaths( 1337 "/", 1338 m_protectedExportPath, 1339 entry.getValue(), 1340 rootPath.substring(entry.getKey().length())); 1341 } 1342 } 1343 return result; 1344 } 1345 1346 /** 1347 * Returns the protected export path.<p> 1348 * 1349 * @return the protected export path 1350 */ 1351 public String getProtectedExportPath() { 1352 1353 return m_protectedExportPath; 1354 } 1355 1356 /** 1357 * Returns the protected export points.<p> 1358 * 1359 * @return the protected export points 1360 */ 1361 public Map<String, String> getProtectedExportPoints() { 1362 1363 return m_protectedExportPoints; 1364 } 1365 1366 /** 1367 * Returns true if the quick plain export is enabled.<p> 1368 * 1369 * @return true if the quick plain export is enabled 1370 */ 1371 public boolean getQuickPlainExport() { 1372 1373 return m_quickPlainExport; 1374 } 1375 1376 /** 1377 * Gets the relative links value as a string representation.<p> 1378 * 1379 * @return <code>"true"</code> or <code>"false"</code> 1380 */ 1381 public String getRelativeLinks() { 1382 1383 return String.valueOf(m_exportRelativeLinks); 1384 } 1385 1386 /** 1387 * Returns the remote address used for internal requests.<p> 1388 * 1389 * @return the remote address 1390 */ 1391 public String getRemoteAddr() { 1392 1393 return m_remoteAddr; 1394 } 1395 1396 /** 1397 * Returns the remote address.<p> 1398 * 1399 * @return the remote address 1400 */ 1401 public String getRemoteAddress() { 1402 1403 return m_remoteAddr; 1404 } 1405 1406 /** 1407 * Returns the static export rfs name for a given vfs resource.<p> 1408 * 1409 * @param cms an initialized cms context 1410 * @param vfsName the name of the vfs resource 1411 * 1412 * @return the static export rfs name for a give vfs resource 1413 * 1414 * @see #getVfsName(CmsObject, String) 1415 * @see #getRfsName(CmsObject, String, String, String) 1416 */ 1417 public String getRfsName(CmsObject cms, String vfsName) { 1418 1419 return getRfsName(cms, vfsName, null, null); 1420 } 1421 1422 /** 1423 * Returns the static export rfs name for a given vfs resource where the link to the 1424 * resource includes request parameters.<p> 1425 * 1426 * @param cms an initialized cms context 1427 * @param vfsName the name of the vfs resource 1428 * @param parameters the parameters of the link pointing to the resource 1429 * @param targetDetailPage the target detail page to use 1430 * 1431 * @return the static export rfs name for a give vfs resource 1432 */ 1433 public String getRfsName(CmsObject cms, String vfsName, String parameters, String targetDetailPage) { 1434 1435 String rfsName; 1436 try { 1437 CmsResource vfsRes = null; 1438 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_4_SERVLET_ACCESS) { 1439 // Accessing the ADEManager during setup may not work. 1440 try { 1441 vfsRes = cms.readResource(vfsName); 1442 I_CmsDetailPageHandler finder = OpenCms.getADEManager().getDetailPageHandler(); 1443 String detailPage = finder.getDetailPage( 1444 cms, 1445 vfsRes.getRootPath(), 1446 cms.getRequestContext().getUri(), 1447 targetDetailPage); 1448 if (detailPage != null) { 1449 vfsName = CmsStringUtil.joinPaths( 1450 detailPage, 1451 CmsDetailPageUtil.getBestUrlName(cms, vfsRes.getStructureId()), 1452 "/"); 1453 } 1454 } catch (CmsVfsResourceNotFoundException e) { 1455 // ignore 1456 } 1457 } 1458 rfsName = getRfsNameWithExportName(cms, vfsName); 1459 String extension = CmsFileUtil.getExtension(rfsName); 1460 // check if the VFS resource is a JSP page with a ".jsp" ending 1461 // in this case the name suffix must be build with special care, 1462 // usually it must be set to ".html" 1463 boolean isJsp = extension.equals(".jsp"); 1464 if (isJsp) { 1465 String suffix = null; 1466 try { 1467 CmsResource res = cms.readResource(vfsName); 1468 isJsp = (CmsResourceTypeJsp.isJsp(res)); 1469 // if the resource is a plain resource then no change in suffix is required 1470 if (isJsp) { 1471 suffix = cms.readPropertyObject( 1472 vfsName, 1473 CmsPropertyDefinition.PROPERTY_EXPORTSUFFIX, 1474 true).getValue(".html"); 1475 } 1476 } catch (CmsVfsResourceNotFoundException e) { 1477 // resource has been deleted, so we are not able to get the right extension from the properties 1478 // try to figure out the right extension from file system 1479 File rfsFile = new File( 1480 CmsFileUtil.normalizePath( 1481 getExportPath(cms.getRequestContext().addSiteRoot(vfsName)) + rfsName)); 1482 File parent = rfsFile.getParentFile(); 1483 if (parent != null) { 1484 File[] paramVariants = parent.listFiles(new CmsPrefixFileFilter(rfsFile.getName())); 1485 if ((paramVariants != null) && (paramVariants.length > 0)) { 1486 // take the first 1487 suffix = paramVariants[0].getAbsolutePath().substring(rfsFile.getAbsolutePath().length()); 1488 } 1489 } else { 1490 // if no luck, try the default extension 1491 suffix = ".html"; 1492 } 1493 } 1494 if ((suffix != null) && !extension.equals(suffix.toLowerCase())) { 1495 rfsName += suffix; 1496 extension = suffix; 1497 } 1498 } 1499 if (parameters != null) { 1500 // build the RFS name for the link with parameters 1501 rfsName = getRfsPath(rfsName, extension, parameters); 1502 // we have found a rfs name for a vfs resource with parameters, save it to the database 1503 try { 1504 cms.writeStaticExportPublishedResource( 1505 rfsName, 1506 CmsStaticExportManager.EXPORT_LINK_WITH_PARAMETER, 1507 parameters, 1508 System.currentTimeMillis()); 1509 } catch (CmsException e) { 1510 LOG.error(Messages.get().getBundle().key(Messages.LOG_WRITE_FAILED_1, rfsName), e); 1511 } 1512 } 1513 } catch (CmsException e) { 1514 if (LOG.isDebugEnabled()) { 1515 LOG.debug(e.getLocalizedMessage(), e); 1516 } 1517 // ignore exception, return vfsName as rfsName 1518 rfsName = vfsName; 1519 } 1520 1521 // add export rfs prefix and return result 1522 1523 if (!vfsName.startsWith(CmsWorkplace.VFS_PATH_SYSTEM) && !OpenCms.getSiteManager().startsWithShared(vfsName)) { 1524 return getRfsPrefix(cms.getRequestContext().addSiteRoot(vfsName)).concat(rfsName); 1525 } else { 1526 // check if we are generating a link to a related resource in the same rfs rule 1527 String source = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri()); 1528 Iterator<CmsStaticExportRfsRule> it = getRfsRules().iterator(); 1529 while (it.hasNext()) { 1530 CmsStaticExportRfsRule rule = it.next(); 1531 if (rule.getSource().matcher(source).matches() && rule.match(vfsName)) { 1532 return rule.getRfsPrefix().concat(rfsName); 1533 } 1534 } 1535 // this is a link across rfs rules 1536 return getRfsPrefix(cms.getRequestContext().getSiteRoot() + "/").concat(rfsName); 1537 } 1538 } 1539 1540 /** 1541 * Returns the prefix for exported links in the "real" file system.<p> 1542 * 1543 * The returned value will be a directory like prefix. The value is configured 1544 * in the <code>opencms-importexport.xml</code> configuration file. An optimization 1545 * of the configured value will be performed, where all relative path information is resolved 1546 * (for example <code>/export/../static</code> will be resolved to <code>/export</code>. 1547 * Moreover, if the configured path ends with a <code>/</code>, this will be cut off 1548 * (for example <code>/export/</code> becomes <code>/export</code>.<p> 1549 * 1550 * This is resource name based, and based on the rfs-rules defined in the 1551 * <code>opencms-importexport.xml</code> configuration file.<p> 1552 * 1553 * @param vfsName the name of the resource to export 1554 * 1555 * @return the prefix for exported links in the "real" file system 1556 * 1557 * @see #getExportPath(String) 1558 * @see #getVfsPrefix() 1559 */ 1560 public String getRfsPrefix(String vfsName) { 1561 1562 if (vfsName != null) { 1563 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 1564 while (it.hasNext()) { 1565 CmsStaticExportRfsRule rule = it.next(); 1566 if (rule.getSource().matcher(vfsName).matches()) { 1567 return rule.getRfsPrefix(); 1568 } 1569 } 1570 } 1571 return m_rfsPrefix; 1572 } 1573 1574 /** 1575 * Returns the original configured prefix for exported links in the "real" file, to be used 1576 * when re-writing the configuration.<p> 1577 * 1578 * This is required <b>only</b> to serialize the configuration again exactly as it was configured. 1579 * This method should <b>not</b> be used otherwise. Use <code>{@link #getRfsPrefix(String)}</code> 1580 * to obtain the rfs prefix to use for the exported links.<p> 1581 * 1582 * @return the original configured prefix for exported links in the "real" file 1583 */ 1584 public String getRfsPrefixForConfiguration() { 1585 1586 return m_rfsPrefixConfigured; 1587 } 1588 1589 /** 1590 * Returns the rfs Rules.<p> 1591 * 1592 * @return the rfs Rules 1593 */ 1594 public List<CmsStaticExportRfsRule> getRfsRules() { 1595 1596 return Collections.unmodifiableList(m_rfsRules); 1597 } 1598 1599 /** 1600 * Returns the shared cache configuration.<p> 1601 * 1602 * @return the shared cache configuration 1603 */ 1604 public CmsSharedCacheConfiguration getSharedCacheConfiguration() { 1605 1606 return m_sharedCacheConfiguration; 1607 } 1608 1609 /** 1610 * Returns the stored content delivery configuration.<p> 1611 * 1612 * @return the stored content delivery configuration 1613 */ 1614 public CmsStoredContentDeliveryConfiguration getStoredContentDeliveryConfiguration() { 1615 1616 return m_storedContentDeliveryConfiguration; 1617 } 1618 1619 /** 1620 * Returns the vfs name of the test resource.<p> 1621 * 1622 * @return the vfs name of the test resource. 1623 */ 1624 public String getTestResource() { 1625 1626 return m_testResource; 1627 } 1628 1629 /** 1630 * Returns the export data for a requested resource, if null is returned no export is required.<p> 1631 * 1632 * @param cms an initialized cms context (should be initialized with the "Guest" user only 1633 * @param vfsName the VFS name of the resource requested 1634 * 1635 * @return the export data for the request, if null is returned no export is required 1636 */ 1637 public CmsStaticExportData getVfsExportData(CmsObject cms, String vfsName) { 1638 1639 return getRfsExportData(cms, getRfsName(cms, vfsName)); 1640 } 1641 1642 /** 1643 * Returns the VFS name for the given RFS name, being the exact reverse of <code>{@link #getRfsName(CmsObject, String)}</code>.<p> 1644 * 1645 * Returns <code>null</code> if no matching VFS resource can be found for the given RFS name.<p> 1646 * 1647 * @param cms the current users OpenCms context 1648 * @param rfsName the RFS name to get the VFS name for 1649 * 1650 * @return the VFS name for the given RFS name, or <code>null</code> if the RFS name does not match to the VFS 1651 * 1652 * @see #getRfsName(CmsObject, String) 1653 */ 1654 public String getVfsName(CmsObject cms, String rfsName) { 1655 1656 CmsStaticExportData data = getRfsExportData(cms, rfsName); 1657 if (data != null) { 1658 String result = data.getVfsName(); 1659 if ((result != null) && result.startsWith(cms.getRequestContext().getSiteRoot())) { 1660 result = result.substring(cms.getRequestContext().getSiteRoot().length()); 1661 } 1662 return result; 1663 } 1664 return null; 1665 } 1666 1667 /** 1668 * Returns the VFS name from a given RFS name.<p> 1669 * 1670 * The RFS name must not contain the RFS prefix.<p> 1671 * 1672 * @param cms an initialized OpenCms user context 1673 * @param rfsName the name of the RFS resource 1674 * 1675 * @return the name of the VFS resource 1676 * 1677 * @throws CmsVfsResourceNotFoundException if something goes wrong 1678 */ 1679 public CmsStaticExportData getVfsNameInternal(CmsObject cms, String rfsName) 1680 throws CmsVfsResourceNotFoundException { 1681 1682 String storedSiteRoot = cms.getRequestContext().getSiteRoot(); 1683 CmsSite currentSite = OpenCms.getSiteManager().getSiteForSiteRoot(storedSiteRoot); 1684 try { 1685 cms.getRequestContext().setSiteRoot("/"); 1686 1687 // try to find a match with the "exportname" folders 1688 String path = rfsName; 1689 // in case of folders, remove the trailing slash 1690 // in case of files, remove the filename and trailing slash 1691 path = path.substring(0, path.lastIndexOf('/')); 1692 // cache the export names 1693 Map<CmsExportname, String> exportnameMapping = getExportnames(); 1694 // in case of folders, remove the trailing slash and in case of files, remove the filename and trailing slash 1695 while (true) { 1696 // exportnameResources are only folders! 1697 String expName = exportnameMapping.get(new CmsExportname(path + "/", currentSite)); 1698 if (expName == null) { 1699 expName = exportnameMapping.get(new CmsExportname(path + "/", null)); 1700 } 1701 if (expName == null) { 1702 if (path.length() == 0) { 1703 break; 1704 } 1705 path = path.substring(0, path.lastIndexOf('/')); 1706 continue; 1707 } 1708 // this will be a root path! 1709 String vfsName = expName + rfsName.substring(path.length() + 1); 1710 try { 1711 return readResource(cms, vfsName); 1712 } catch (CmsVfsResourceNotFoundException e) { 1713 // if already checked all parts of the path we can stop here. 1714 // This is the case if the "/" is set as "exportname" on any vfs resource 1715 if (path.length() == 0) { 1716 break; 1717 } 1718 // continue with trying out the other exportname to find a match (may be a multiple prefix) 1719 path = path.substring(0, path.lastIndexOf('/')); 1720 continue; 1721 } catch (CmsException e) { 1722 // should never happen 1723 LOG.error(e.getLocalizedMessage(), e); 1724 break; 1725 } 1726 } 1727 1728 // try to read name of export resource by reading the resource directly 1729 try { 1730 return readResource(cms, rfsName); 1731 } catch (Throwable t) { 1732 // resource not found 1733 if (LOG.isDebugEnabled()) { 1734 LOG.debug( 1735 Messages.get().getBundle().key(Messages.ERR_EXPORT_FILE_FAILED_1, new String[] {rfsName}), 1736 t); 1737 } 1738 } 1739 1740 // finally check if its a modified jsp resource 1741 int extPos = rfsName.lastIndexOf('.'); 1742 // first cut of the last extension 1743 if (extPos >= 0) { 1744 String cutName = rfsName.substring(0, extPos); 1745 int pos = cutName.lastIndexOf('.'); 1746 if (pos >= 0) { 1747 // now check if remaining String ends with ".jsp" 1748 String extension = cutName.substring(pos).toLowerCase(); 1749 if (".jsp".equals(extension)) { 1750 return getVfsNameInternal(cms, cutName); 1751 } 1752 } 1753 } 1754 } finally { 1755 cms.getRequestContext().setSiteRoot(storedSiteRoot); 1756 } 1757 throw new CmsVfsResourceNotFoundException( 1758 org.opencms.db.generic.Messages.get().container( 1759 org.opencms.db.generic.Messages.ERR_READ_RESOURCE_1, 1760 rfsName)); 1761 } 1762 1763 /** 1764 * Returns the prefix for the internal in the VFS.<p> 1765 * 1766 * The returned value will be a directory like prefix. The value is configured 1767 * in the <code>opencms-importexport.xml</code> configuration file. An optimization 1768 * of the configured value will be performed, where all relative path information is resolved 1769 * (for example <code>/opencms/../mycms</code> will be resolved to <code>/mycms</code>. 1770 * Moreover, if the configured path ends with a <code>/</code>, this will be cut off 1771 * (for example <code>/opencms/</code> becomes <code>/opencms</code>.<p> 1772 * 1773 * @return the prefix for the internal in the VFS 1774 * 1775 * @see #getExportPath(String) 1776 * @see #getRfsPrefix(String) 1777 */ 1778 public String getVfsPrefix() { 1779 1780 return m_vfsPrefix; 1781 } 1782 1783 /** 1784 * Returns the original configured prefix for internal links in the VFS, to be used 1785 * when re-writing the configuration.<p> 1786 * 1787 * This is required <b>only</b> to serialize the configuration again exactly as it was configured. 1788 * This method should <b>not</b> be used otherwise. Use <code>{@link #getVfsPrefix()}</code> 1789 * to obtain the VFS prefix to use for the internal links.<p> 1790 * 1791 * @return the original configured prefix for internal links in the VFS 1792 */ 1793 public String getVfsPrefixForConfiguration() { 1794 1795 return m_vfsPrefixConfigured; 1796 } 1797 1798 /** 1799 * Initializes the static export manager with the OpenCms system configuration.<p> 1800 * 1801 * @param cms an OpenCms context object 1802 */ 1803 public void initialize(CmsObject cms) { 1804 1805 m_adminCms = cms; 1806 // initialize static export RFS path (relative to web application) 1807 m_staticExportPath = normalizeExportPath(m_staticExportPathConfigured); 1808 m_staticExportWorkPath = normalizeExportPath(getExportWorkPathForConfiguration()); 1809 if (m_staticExportPath.equals(OpenCms.getSystemInfo().getWebApplicationRfsPath())) { 1810 throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INVALID_EXPORT_PATH_0)); 1811 } 1812 // initialize prefix variables 1813 m_rfsPrefix = normalizeRfsPrefix(m_rfsPrefixConfigured); 1814 Iterator<CmsStaticExportRfsRule> itRfsRules = m_rfsRules.iterator(); 1815 while (itRfsRules.hasNext()) { 1816 CmsStaticExportRfsRule rule = itRfsRules.next(); 1817 try { 1818 rule.setExportPath(normalizeExportPath(rule.getExportPathConfigured())); 1819 } catch (CmsIllegalArgumentException e) { 1820 CmsLog.INIT.warn(e.getMessageContainer()); 1821 rule.setExportPath(m_staticExportPath); 1822 } 1823 try { 1824 rule.setExportWorkPath(normalizeExportPath(rule.getExportWorkPathConfigured())); 1825 } catch (CmsIllegalArgumentException e) { 1826 CmsLog.INIT.warn(e.getMessageContainer()); 1827 rule.setExportWorkPath(m_staticExportWorkPath); 1828 } 1829 rule.setRfsPrefix(normalizeRfsPrefix(rule.getRfsPrefixConfigured())); 1830 } 1831 m_vfsPrefix = insertContextStrings(m_vfsPrefixConfigured); 1832 m_vfsPrefix = CmsFileUtil.normalizePath(m_vfsPrefix, '/'); 1833 if (CmsResource.isFolder(m_vfsPrefix)) { 1834 // ensure prefix does NOT end with a folder '/' 1835 m_vfsPrefix = m_vfsPrefix.substring(0, m_vfsPrefix.length() - 1); 1836 } 1837 if (CmsLog.INIT.isDebugEnabled()) { 1838 if (cms != null) { 1839 CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_SE_MANAGER_CREATED_1, cms)); 1840 } else { 1841 CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_SE_MANAGER_CREATED_0)); 1842 } 1843 } 1844 1845 m_cacheOnlineLinks = CmsMemoryMonitor.createLRUCacheMap(2048); 1846 OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheOnlineLinks", m_cacheOnlineLinks); 1847 1848 m_cacheExportUris = CmsMemoryMonitor.createLRUCacheMap(2048); 1849 OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportUris", m_cacheExportUris); 1850 1851 m_cacheSecureLinks = CmsMemoryMonitor.createLRUCacheMap(2048); 1852 OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheSecureLinks", m_cacheSecureLinks); 1853 1854 m_cacheExportLinks = CmsMemoryMonitor.createLRUCacheMap(2048); 1855 OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportLinks", m_cacheExportLinks); 1856 1857 // register this object as event listener 1858 OpenCms.addCmsEventListener( 1859 this, 1860 new int[] { 1861 I_CmsEventListener.EVENT_PUBLISH_PROJECT, 1862 I_CmsEventListener.EVENT_CLEAR_CACHES, 1863 I_CmsEventListener.EVENT_UPDATE_EXPORTS}); 1864 1865 m_exportFolderMatcher = new CmsExportFolderMatcher(m_exportFolders, m_testResource); 1866 1867 // get the default accept-language header value 1868 m_defaultAcceptLanguageHeader = CmsAcceptLanguageHeaderParser.createLanguageHeader(); 1869 1870 // get the default accept-charset header value 1871 m_defaultAcceptCharsetHeader = OpenCms.getSystemInfo().getDefaultEncoding(); 1872 1873 // get the export url prefix 1874 int pos = m_exportUrl.indexOf("://"); 1875 if (pos > 0) { 1876 // absolute link, remove http://servername 1877 int pos2 = m_exportUrl.indexOf('/', pos + 3); 1878 if (pos2 > 0) { 1879 m_exportUrlPrefix = m_exportUrl.substring(pos2); 1880 } else { 1881 // should never happen 1882 m_exportUrlPrefix = ""; 1883 } 1884 } else { 1885 m_exportUrlPrefix = m_exportUrl; 1886 } 1887 if (CmsLog.INIT.isInfoEnabled()) { 1888 if (isStaticExportEnabled()) { 1889 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STATIC_EXPORT_ENABLED_0)); 1890 CmsLog.INIT.info( 1891 Messages.get().getBundle().key( 1892 Messages.INIT_EXPORT_DEFAULT_1, 1893 Boolean.valueOf(getExportPropertyDefault()))); 1894 itRfsRules = m_rfsRules.iterator(); 1895 while (itRfsRules.hasNext()) { 1896 CmsStaticExportRfsRule rfsRule = itRfsRules.next(); 1897 CmsLog.INIT.info( 1898 Messages.get().getBundle().key( 1899 Messages.INIT_EXPORT_RFS_RULE_EXPORT_PATH_2, 1900 rfsRule.getSource(), 1901 rfsRule.getExportPath())); 1902 CmsLog.INIT.info( 1903 Messages.get().getBundle().key( 1904 Messages.INIT_EXPORT_RFS_RULE_RFS_PREFIX_2, 1905 rfsRule.getSource(), 1906 rfsRule.getRfsPrefix())); 1907 if (rfsRule.getUseRelativeLinks() != null) { 1908 if (rfsRule.getUseRelativeLinks().booleanValue()) { 1909 CmsLog.INIT.info( 1910 Messages.get().getBundle().key( 1911 Messages.INIT_EXPORT_RFS_RULE_RELATIVE_LINKS_1, 1912 rfsRule.getSource())); 1913 } else { 1914 CmsLog.INIT.info( 1915 Messages.get().getBundle().key( 1916 Messages.INIT_EXPORT_RFS_RULE_ABSOLUTE_LINKS_1, 1917 rfsRule.getSource())); 1918 } 1919 } 1920 } 1921 // default rule 1922 CmsLog.INIT.info( 1923 Messages.get().getBundle().key( 1924 Messages.INIT_EXPORT_RFS_RULE_EXPORT_PATH_2, 1925 "/", 1926 m_staticExportPath)); 1927 CmsLog.INIT.info( 1928 Messages.get().getBundle().key(Messages.INIT_EXPORT_RFS_RULE_RFS_PREFIX_2, "/", m_rfsPrefix)); 1929 if (m_exportRelativeLinks) { 1930 CmsLog.INIT.info( 1931 Messages.get().getBundle().key(Messages.INIT_EXPORT_RFS_RULE_RELATIVE_LINKS_1, "/")); 1932 } else { 1933 CmsLog.INIT.info( 1934 Messages.get().getBundle().key(Messages.INIT_EXPORT_RFS_RULE_ABSOLUTE_LINKS_1, "/")); 1935 } 1936 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_VFS_PREFIX_1, getVfsPrefix())); 1937 CmsLog.INIT.info( 1938 Messages.get().getBundle().key( 1939 Messages.INIT_EXPORT_EXPORT_HANDLER_1, 1940 getHandler().getClass().getName())); 1941 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_URL_1, getExportUrl())); 1942 CmsLog.INIT.info( 1943 Messages.get().getBundle().key(Messages.INIT_EXPORT_OPTIMIZATION_1, getPlainExportOptimization())); 1944 CmsLog.INIT.info( 1945 Messages.get().getBundle().key(Messages.INIT_EXPORT_TESTRESOURCE_1, getTestResource())); 1946 CmsLog.INIT.info( 1947 Messages.get().getBundle().key( 1948 Messages.INIT_LINKSUBSTITUTION_HANDLER_1, 1949 getLinkSubstitutionHandler().getClass().getName())); 1950 } else { 1951 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STATIC_EXPORT_DISABLED_0)); 1952 } 1953 } 1954 } 1955 1956 /** 1957 * Checks if the static export is required for the given VFS resource.<p> 1958 * 1959 * Please note that the given OpenCms user context is NOT used to read the resource. 1960 * The check for export is always done with the permissions of the "Export" user. 1961 * The provided user context is just used to get the current site root.<p> 1962 * 1963 * Since the "Export" user always operates in the "Online" project, the resource 1964 * is also read from the "Online" project, not from the current project of the given 1965 * OpenCms context.<p> 1966 * 1967 * @param cms the current users OpenCms context 1968 * @param vfsName the VFS resource name to check 1969 * 1970 * @return <code>true</code> if static export is required for the given VFS resource 1971 */ 1972 public boolean isExportLink(CmsObject cms, String vfsName) { 1973 1974 LOG.info("isExportLink? " + vfsName); 1975 if (!isStaticExportEnabled()) { 1976 return false; 1977 } 1978 String siteRoot = cms.getRequestContext().getSiteRoot(); 1979 // vfsname may still be a root path for a site with a different site root 1980 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(vfsName); 1981 if (site != null) { 1982 siteRoot = site.getSiteRoot(); 1983 vfsName = CmsStringUtil.joinPaths("/", vfsName.substring(siteRoot.length())); 1984 } 1985 String cacheKey = getCacheKey(siteRoot, vfsName); 1986 Boolean exportResource = getCacheExportLinks().get(cacheKey); 1987 if (exportResource != null) { 1988 return exportResource.booleanValue(); 1989 } 1990 1991 boolean result = false; 1992 try { 1993 // static export must always be checked with the export users permissions, 1994 // not the current users permissions 1995 CmsObject exportCms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); 1996 exportCms.getRequestContext().setSiteRoot(siteRoot); 1997 // exportRes is usually the resource at path vfsName, but in case of detail page URIs it's the detail content 1998 CmsResource exportRes = CmsDetailPageUtil.lookupPage(exportCms, vfsName); 1999 // if we are handling request for robots.txt, don't export 2000 if (OpenCms.getResourceManager().matchResourceType( 2001 CmsXmlSeoConfiguration.SEO_FILE_TYPE, 2002 exportRes.getTypeId())) { 2003 if (vfsName.endsWith("robots.txt")) { 2004 return false; 2005 } 2006 } 2007 2008 if (Strings.builder().get().endsWithAny(vfsName.toLowerCase(), ".jsp", ".jspx") 2009 && !OpenCms.getResourceManager().matchResourceType( 2010 CmsResourceTypeJsp.getStaticTypeName(), 2011 exportRes.getTypeId())) { 2012 2013 return false; 2014 } 2015 String exportValue = exportCms.readPropertyObject( 2016 exportCms.getSitePath(exportRes), 2017 CmsPropertyDefinition.PROPERTY_EXPORT, 2018 true).getValue(); 2019 if (exportValue == null) { 2020 // no setting found for "export" property 2021 if (getExportPropertyDefault()) { 2022 // if the default is "true" we always export 2023 result = true; 2024 } else { 2025 // check if the resource is exportable by suffix 2026 result = isSuffixExportable(vfsName); 2027 } 2028 } else { 2029 // "export" value found, if it was "true" we export 2030 result = Boolean.valueOf(exportValue).booleanValue(); 2031 } 2032 } catch (CmsException e) { 2033 // no export required (probably security issues, e.g. no access for export user) 2034 LOG.debug(e.getLocalizedMessage(), e); 2035 } 2036 getCacheExportLinks().put(cacheKey, Boolean.valueOf(result)); 2037 2038 return result; 2039 } 2040 2041 /** 2042 * Returns true if the export process is a full static export.<p> 2043 * 2044 * @return true if the export process is a full static export 2045 */ 2046 public boolean isFullStaticExport() { 2047 2048 return m_fullStaticExport; 2049 } 2050 2051 /** 2052 * Returns <code>true</code> if the given VFS resource should be transported through a secure channel.<p> 2053 * 2054 * The secure mode is only checked in the "Online" project. 2055 * If the given OpenCms context is currently not in the "Online" project, 2056 * <code>false</code> is returned.<p> 2057 * 2058 * The given resource is read from the site root of the provided OpenCms context.<p> 2059 * 2060 * @param cms the current users OpenCms context 2061 * @param vfsName the VFS resource name to check 2062 * 2063 * @return <code>true</code> if the given VFS resource should be transported through a secure channel 2064 * 2065 * @see CmsStaticExportManager#isSecureLink(CmsObject, String, String) 2066 */ 2067 public boolean isSecureLink(CmsObject cms, String vfsName) { 2068 2069 return isSecureLink(cms, vfsName, false); 2070 } 2071 2072 /** 2073 * Returns <code>true</code> if the given VFS resource should be transported through a secure channel.<p> 2074 * 2075 * The secure mode is only checked in the "Online" project. 2076 * If the given OpenCms context is currently not in the "Online" project, 2077 * <code>false</code> is returned.<p> 2078 * 2079 * The given resource is read from the site root of the provided OpenCms context.<p> 2080 * 2081 * @param cms the current users OpenCms context 2082 * @param vfsName the VFS resource name to check 2083 * @param fromSecure <code>true</code> if the link source is delivered secure 2084 * 2085 * @return <code>true</code> if the given VFS resource should be transported through a secure channel 2086 * 2087 * @see CmsStaticExportManager#isSecureLink(CmsObject, String, String) 2088 */ 2089 public boolean isSecureLink(CmsObject cms, String vfsName, boolean fromSecure) { 2090 2091 if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) { 2092 return false; 2093 } 2094 2095 String cacheKey = OpenCms.getStaticExportManager().getCacheKey(cms.getRequestContext().getSiteRoot(), vfsName); 2096 String secureResource = OpenCms.getStaticExportManager().getCacheSecureLinks().get(cacheKey); 2097 if (secureResource == null) { 2098 CmsObject cmsForReadingProperties = cms; 2099 try { 2100 // the link target resource may not be readable by the current user, so we use a CmsObject with admin permissions 2101 // to read the "secure" property 2102 CmsObject adminCms = OpenCms.initCmsObject(m_adminCms); 2103 adminCms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot()); 2104 adminCms.getRequestContext().setCurrentProject(cms.getRequestContext().getCurrentProject()); 2105 adminCms.getRequestContext().setRequestTime(cms.getRequestContext().getRequestTime()); 2106 cmsForReadingProperties = adminCms; 2107 } catch (Exception e) { 2108 LOG.error("Could not initialize CmsObject in isSecureLink:" + e.getLocalizedMessage(), e); 2109 } 2110 try { 2111 secureResource = cmsForReadingProperties.readPropertyObject( 2112 vfsName, 2113 CmsPropertyDefinition.PROPERTY_SECURE, 2114 true).getValue(); 2115 if (CmsStringUtil.isEmptyOrWhitespaceOnly(secureResource)) { 2116 secureResource = "false"; 2117 } 2118 // only cache result if read was successfull 2119 OpenCms.getStaticExportManager().getCacheSecureLinks().put(cacheKey, secureResource); 2120 } catch (CmsVfsResourceNotFoundException e) { 2121 secureResource = SECURE_PROPERTY_VALUE_BOTH; 2122 OpenCms.getStaticExportManager().getCacheSecureLinks().put(cacheKey, secureResource); 2123 } catch (Exception e) { 2124 // no secure link required (probably security issues, e.g. no access for current user) 2125 // however other users may be allowed to read the resource, so the result can't be cached 2126 return false; 2127 } 2128 } 2129 return Boolean.parseBoolean(secureResource) 2130 || (fromSecure && SECURE_PROPERTY_VALUE_BOTH.equals(secureResource)); 2131 } 2132 2133 /** 2134 * Returns <code>true</code> if the given VFS resource that is located under the 2135 * given site root should be transported through a secure channel.<p> 2136 * 2137 * @param cms the current users OpenCms context 2138 * @param vfsName the VFS resource name to check 2139 * @param siteRoot the site root where the the VFS resource should be read 2140 * 2141 * @return <code>true</code> if the given VFS resource should be transported through a secure channel 2142 * 2143 * @see #isSecureLink(CmsObject, String) 2144 */ 2145 public boolean isSecureLink(CmsObject cms, String vfsName, String siteRoot) { 2146 2147 return isSecureLink(cms, vfsName, siteRoot, false); 2148 } 2149 2150 /** 2151 * Returns <code>true</code> if the given VFS resource should be transported through a secure channel.<p> 2152 * 2153 * The secure mode is only checked in the "Online" project. 2154 * If the given OpenCms context is currently not in the "Online" project, 2155 * <code>false</code> is returned.<p> 2156 * 2157 * The given resource is read from the site root of the provided OpenCms context.<p> 2158 * 2159 * @param cms the current users OpenCms context 2160 * @param vfsName the VFS resource name to check 2161 * @param siteRoot the site root where the the VFS resource should be read 2162 * @param fromSecure <code>true</code> if the link source is delivered secure 2163 * 2164 * @return <code>true</code> if the given VFS resource should be transported through a secure channel 2165 * 2166 * @see CmsStaticExportManager#isSecureLink(CmsObject, String, String) 2167 */ 2168 public boolean isSecureLink(CmsObject cms, String vfsName, String siteRoot, boolean fromSecure) { 2169 2170 if (siteRoot == null) { 2171 return isSecureLink(cms, vfsName, fromSecure); 2172 } 2173 2174 // the site root of the cms object has to be changed so that the property can be read 2175 String storedSiteRoot = cms.getRequestContext().getSiteRoot(); 2176 try { 2177 cms.getRequestContext().setSiteRoot(siteRoot); 2178 return isSecureLink(cms, vfsName, fromSecure); 2179 } finally { 2180 cms.getRequestContext().setSiteRoot(storedSiteRoot); 2181 } 2182 } 2183 2184 /** 2185 * Returns true if the static export is enabled.<p> 2186 * 2187 * @return true if the static export is enabled 2188 */ 2189 public boolean isStaticExportEnabled() { 2190 2191 return m_staticExportEnabled; 2192 } 2193 2194 /** 2195 * Returns true if the given resource name is exportable because of it's suffix.<p> 2196 * 2197 * @param resourceName the name to check 2198 * @return true if the given resource name is exportable because of it's suffix 2199 */ 2200 public boolean isSuffixExportable(String resourceName) { 2201 2202 if (resourceName == null) { 2203 return false; 2204 } 2205 int pos = resourceName.lastIndexOf('.'); 2206 if (pos >= 0) { 2207 String suffix = resourceName.substring(pos).toLowerCase(); 2208 return m_exportSuffixes.contains(suffix); 2209 } 2210 return false; 2211 } 2212 2213 /** 2214 * Checks if we have to use temporary directories during export.<p> 2215 * 2216 * @return <code>true</code> if using temporary directories 2217 */ 2218 public boolean isUseTempDir() { 2219 2220 return m_useTempDirs; 2221 } 2222 2223 /** 2224 * Returns true if the links in the static export should be relative.<p> 2225 * 2226 * @param vfsName the name of the resource to export 2227 * 2228 * @return true if the links in the static export should be relative 2229 */ 2230 public boolean relativeLinksInExport(String vfsName) { 2231 2232 if (vfsName != null) { 2233 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 2234 while (it.hasNext()) { 2235 CmsStaticExportRfsRule rule = it.next(); 2236 if (rule.getSource().matcher(vfsName).matches()) { 2237 return rule.getUseRelativeLinks() != null 2238 ? rule.getUseRelativeLinks().booleanValue() 2239 : m_exportRelativeLinks; 2240 } 2241 } 2242 } 2243 return m_exportRelativeLinks; 2244 } 2245 2246 /** 2247 * Removes a local static export file for content delivered directly from storage.<p> 2248 * 2249 * @param exportFileName the export file name 2250 * 2251 * @throws CmsException if the file can not be deleted 2252 */ 2253 public void removeStoredContentExportFile(String exportFileName) throws CmsException { 2254 2255 File exportFile = new File(exportFileName); 2256 if (!exportFile.exists()) { 2257 return; 2258 } 2259 if (!exportFile.delete()) { 2260 throw new CmsStaticExportException(Messages.get().container(Messages.ERR_OUTPUT_STREAM_1, exportFileName)); 2261 } 2262 if (LOG.isDebugEnabled()) { 2263 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FILE_DELETED_1, exportFileName)); 2264 } 2265 } 2266 2267 /** 2268 * Sets the accept-charset header value.<p> 2269 * 2270 * @param value accept-language header value 2271 */ 2272 public void setAcceptCharsetHeader(String value) { 2273 2274 m_acceptCharsetHeader = value; 2275 } 2276 2277 /** 2278 * Sets the accept-language header value.<p> 2279 * 2280 * @param value accept-language header value 2281 */ 2282 public void setAcceptLanguageHeader(String value) { 2283 2284 m_acceptLanguageHeader = value; 2285 } 2286 2287 /** 2288 * Sets the default property value.<p> 2289 * 2290 * @param value must be <code>true</code> or <code>false</code> 2291 */ 2292 public void setDefault(String value) { 2293 2294 m_exportPropertyDefault = Boolean.valueOf(value).booleanValue(); 2295 } 2296 2297 /** 2298 * Sets the number of backups for the static export.<p> 2299 * 2300 * @param backup number of backups 2301 */ 2302 public void setExportBackups(String backup) { 2303 2304 m_staticExportBackups = Integer.valueOf(backup); 2305 } 2306 2307 /** 2308 * Sets the export enabled value.<p> 2309 * 2310 * @param value must be <code>true</code> or <code>false</code> 2311 */ 2312 public void setExportEnabled(String value) { 2313 2314 m_staticExportEnabled = Boolean.valueOf(value).booleanValue(); 2315 } 2316 2317 /** 2318 * Adds a resource pattern to the list of resources which are part of the export.<p> 2319 * 2320 * @param folder the folder pattern to add to the list. 2321 */ 2322 public void setExportFolderPattern(String folder) { 2323 2324 m_exportFolders.add(folder); 2325 } 2326 2327 /** 2328 * Sets specific http header for the static export.<p> 2329 * 2330 * The format of the headers must be "header:value".<p> 2331 * 2332 * @param exportHeader a specific http header 2333 */ 2334 public void setExportHeader(String exportHeader) { 2335 2336 if (CmsStringUtil.splitAsArray(exportHeader, ':').length == 2) { 2337 if (CmsLog.INIT.isInfoEnabled()) { 2338 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_HEADERS_1, exportHeader)); 2339 } 2340 m_exportHeaders.add(exportHeader); 2341 } else { 2342 if (CmsLog.INIT.isWarnEnabled()) { 2343 CmsLog.INIT.warn(Messages.get().getBundle().key(Messages.INIT_INVALID_HEADER_1, exportHeader)); 2344 } 2345 } 2346 } 2347 2348 /** 2349 * Sets the path where the static export is written.<p> 2350 * 2351 * @param path the path where the static export is written 2352 */ 2353 public void setExportPath(String path) { 2354 2355 m_staticExportPathConfigured = path; 2356 } 2357 2358 /** 2359 * Adds a suffix to the list of resource suffixes which will be exported by default.<p> 2360 * 2361 * @param suffix the suffix to add to the list. 2362 */ 2363 public void setExportSuffix(String suffix) { 2364 2365 m_exportSuffixes.add(suffix.toLowerCase()); 2366 } 2367 2368 /** 2369 * Sets the export url.<p> 2370 * 2371 * @param url the export url 2372 */ 2373 public void setExportUrl(String url) { 2374 2375 m_exportUrl = insertContextStrings(url); 2376 m_exportUrlConfigured = url; 2377 } 2378 2379 /** 2380 * Sets the path where the static export is temporarily written.<p> 2381 * 2382 * @param path the path where the static export is temporarily written 2383 */ 2384 public void setExportWorkPath(String path) { 2385 2386 m_staticExportWorkPathConfigured = path; 2387 } 2388 2389 /** 2390 * Sets the link substitution handler class.<p> 2391 * 2392 * @param handlerClassName the link substitution handler class name 2393 */ 2394 public void setHandler(String handlerClassName) { 2395 2396 try { 2397 m_handler = (I_CmsStaticExportHandler)Class.forName(handlerClassName).newInstance(); 2398 } catch (Exception e) { 2399 // should never happen 2400 LOG.error(e.getLocalizedMessage(), e); 2401 } 2402 } 2403 2404 /** 2405 * Sets the static export handler class.<p> 2406 * 2407 * @param handlerClassName the static export handler class name 2408 */ 2409 public void setLinkSubstitutionHandler(String handlerClassName) { 2410 2411 try { 2412 m_linkSubstitutionHandler = (I_CmsLinkSubstitutionHandler)Class.forName(handlerClassName).newInstance(); 2413 } catch (Exception e) { 2414 // should never happen 2415 LOG.error(e.getLocalizedMessage(), e); 2416 } 2417 } 2418 2419 /** 2420 * Sets the plain export optimization value.<p> 2421 * 2422 * @param value must be <code>true</code> or <code>false</code> 2423 */ 2424 public void setPlainExportOptimization(String value) { 2425 2426 m_quickPlainExport = Boolean.valueOf(value).booleanValue(); 2427 } 2428 2429 /** 2430 * Sets the protected export path.<p> 2431 * 2432 * @param exportPath the export path to set 2433 */ 2434 public void setProtectedExportPath(String exportPath) { 2435 2436 m_protectedExportPath = exportPath; 2437 } 2438 2439 /** 2440 * Sets the relative links value.<p> 2441 * 2442 * @param value must be <code>true</code> or <code>false</code> 2443 */ 2444 public void setRelativeLinks(String value) { 2445 2446 m_exportRelativeLinks = Boolean.valueOf(value).booleanValue(); 2447 } 2448 2449 /** 2450 * Sets the remote address which will be used for internal requests during the static export.<p> 2451 * 2452 * @param addr the remote address to be used 2453 */ 2454 public void setRemoteAddr(String addr) { 2455 2456 m_remoteAddr = addr; 2457 } 2458 2459 /** 2460 * Sets the prefix for exported links in the "real" file system.<p> 2461 * 2462 * @param rfsPrefix the prefix for exported links in the "real" file system 2463 */ 2464 public void setRfsPrefix(String rfsPrefix) { 2465 2466 m_rfsPrefixConfigured = rfsPrefix; 2467 } 2468 2469 /** 2470 * Sets the shared cache configuration.<p> 2471 * 2472 * @param configuration the shared cache configuration 2473 */ 2474 public void setSharedCacheConfiguration(CmsSharedCacheConfiguration configuration) { 2475 2476 if (configuration == null) { 2477 m_sharedCacheConfiguration = new CmsSharedCacheConfiguration(); 2478 } else { 2479 m_sharedCacheConfiguration = configuration; 2480 } 2481 } 2482 2483 /** 2484 * Sets the stored content delivery configuration.<p> 2485 * 2486 * @param configuration the stored content delivery configuration 2487 */ 2488 public void setStoredContentDeliveryConfiguration(CmsStoredContentDeliveryConfiguration configuration) { 2489 2490 if (configuration == null) { 2491 m_storedContentDeliveryConfiguration = new CmsStoredContentDeliveryConfiguration(); 2492 } else { 2493 m_storedContentDeliveryConfiguration = configuration; 2494 } 2495 } 2496 2497 /** 2498 * Sets the test resource.<p> 2499 * 2500 * @param testResource the vfs name of the test resource 2501 */ 2502 public void setTestResource(String testResource) { 2503 2504 m_testResource = testResource; 2505 } 2506 2507 /** 2508 * Sets the prefix for internal links in the vfs.<p> 2509 * 2510 * @param vfsPrefix the prefix for internal links in the vfs 2511 */ 2512 public void setVfsPrefix(String vfsPrefix) { 2513 2514 m_vfsPrefixConfigured = vfsPrefix; 2515 } 2516 2517 /** 2518 * Shuts down all this static export manager.<p> 2519 * 2520 * This is required since there may still be a thread running when the system is being shut down.<p> 2521 */ 2522 public synchronized void shutDown() { 2523 2524 int count = 0; 2525 // if the handler is still running, we must wait up to 30 seconds until it is finished 2526 while ((count < HANDLER_FINISH_TIME) && m_handler.isBusy()) { 2527 count++; 2528 try { 2529 if (CmsLog.INIT.isInfoEnabled()) { 2530 CmsLog.INIT.info( 2531 Messages.get().getBundle().key( 2532 Messages.INIT_STATIC_EXPORT_SHUTDOWN_3, 2533 m_handler.getClass().getName(), 2534 String.valueOf(count), 2535 String.valueOf(HANDLER_FINISH_TIME))); 2536 } 2537 wait(1000); 2538 } catch (InterruptedException e) { 2539 // if interrupted we ignore the handler, this will produce some log messages but should be ok 2540 count = HANDLER_FINISH_TIME; 2541 } 2542 } 2543 2544 if (CmsLog.INIT.isInfoEnabled()) { 2545 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName())); 2546 } 2547 2548 } 2549 2550 /** 2551 * Tries to deliver an already resolved scaled image directly from the configured image cache.<p> 2552 * 2553 * This fast path is limited to registered parameterized static-export links which use the exact image loader. 2554 * The caller must have obtained the data through {@link #getExportData}, so the export-link check and parameter 2555 * lookup have already succeeded. The image loader still validates the scale parameters and read permission before 2556 * creating or delivering the derivative. All other image requests continue through {@link #export}.<p> 2557 * 2558 * @param req the wrapped static export request containing the registered scale parameters 2559 * @param res the current response 2560 * @param cms the CMS context initialized with the Export user 2561 * @param data the resolved static export data 2562 * 2563 * @return <code>true</code> if the response has been handled directly 2564 * 2565 * @throws CmsException if accessing the VFS or image cache fails 2566 * @throws IOException if writing the response fails 2567 */ 2568 public boolean tryExportImageCache( 2569 HttpServletRequest req, 2570 HttpServletResponse res, 2571 CmsObject cms, 2572 CmsStaticExportData data) 2573 throws CmsException, IOException { 2574 2575 if ((req == null) 2576 || (res == null) 2577 || (cms == null) 2578 || (data == null) 2579 || (data.getResource() == null) 2580 || (data.getParameters() == null) 2581 || data.isDetailPage()) { 2582 return false; 2583 } 2584 CmsStoredContentDeliveryConfiguration configuration = getStoredContentDeliveryConfiguration(); 2585 CmsResource resource = data.getResource(); 2586 if (!configuration.isEnabled() || !configuration.isSuffixEnabled(resource.getRootPath())) { 2587 return false; 2588 } 2589 I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(resource); 2590 if (!(loader instanceof I_CmsStaticExportDirectResponseLoader) 2591 || (loader.getLoaderId() != CmsImageLoader.RESOURCE_LOADER_ID_IMAGE_LOADER)) { 2592 return false; 2593 } 2594 String mimetype = OpenCms.getResourceManager().getMimeType( 2595 resource.getName(), 2596 cms.getRequestContext().getEncoding()); 2597 if (mimetype != null) { 2598 res.setContentType(mimetype); 2599 } 2600 I_CmsStaticExportDirectResponseLoader directResponseLoader = (I_CmsStaticExportDirectResponseLoader)loader; 2601 return directResponseLoader.tryExportDirectResponse(cms, resource, req, res); 2602 } 2603 2604 /** 2605 * Tries to deliver an already resolved on-demand static export resource directly from external storage.<p> 2606 * 2607 * This is a narrowly constrained fast path for requests which have already passed {@link #getExportData}. 2608 * It deliberately only handles plain dump-loader resources without export parameters. All other resources 2609 * continue through {@link #export}, where locale handling, resource initialization and export rules are applied.<p> 2610 * 2611 * @param req the current request 2612 * @param res the current response 2613 * @param cms the CMS context initialized with the Export user 2614 * @param data the resolved static export data 2615 * 2616 * @return <code>true</code> if the response has been handled directly 2617 * 2618 * @throws CmsException if accessing the VFS or storage fails 2619 * @throws IOException if writing the response fails 2620 */ 2621 public boolean tryExportStoredContent( 2622 HttpServletRequest req, 2623 HttpServletResponse res, 2624 CmsObject cms, 2625 CmsStaticExportData data) 2626 throws CmsException, IOException { 2627 2628 if ((req == null) 2629 || (res == null) 2630 || (cms == null) 2631 || (data == null) 2632 || (data.getResource() == null) 2633 || (data.getParameters() != null) 2634 || data.isDetailPage()) { 2635 return false; 2636 } 2637 CmsStoredContentDeliveryConfiguration configuration = getStoredContentDeliveryConfiguration(); 2638 CmsResource resource = data.getResource(); 2639 if (!configuration.isEnabled() || !configuration.isSuffixEnabled(resource.getRootPath())) { 2640 return false; 2641 } 2642 I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(resource); 2643 if (!(loader instanceof CmsDumpLoader) 2644 || (loader.getLoaderId() != CmsDumpLoader.RESOURCE_LOADER_ID) 2645 || !(loader instanceof I_CmsStoredContentDirectDeliveryLoader)) { 2646 return false; 2647 } 2648 I_CmsStoredContentDirectDeliveryLoader directDeliveryLoader = (I_CmsStoredContentDirectDeliveryLoader)loader; 2649 if (!directDeliveryLoader.isStoredContentDirectDeliveryEnabled(cms, resource, req, res)) { 2650 return false; 2651 } 2652 CmsStaticExportResponseWrapper wrapRes = new CmsStaticExportResponseWrapper(res); 2653 if (!directDeliveryLoader.exportStoredContentTo(cms, resource, req, wrapRes)) { 2654 return false; 2655 } 2656 int status = wrapRes.getStatus(); 2657 res.setStatus(status < 0 ? HttpServletResponse.SC_OK : status); 2658 return true; 2659 } 2660 2661 /** 2662 * Clears the caches in the export manager.<p> 2663 * 2664 * @param event the event that requested to clear the caches 2665 */ 2666 protected void clearCaches(CmsEvent event) { 2667 2668 // synchronization of this method is not required as the individual maps are all synchronized maps anyway, 2669 // and setExportnames() is doing it's own synchronization 2670 2671 // flush all caches 2672 m_cacheOnlineLinks.clear(); 2673 m_cacheExportUris.clear(); 2674 m_cacheSecureLinks.clear(); 2675 m_cacheExportLinks.clear(); 2676 m_exportnameResources = null; 2677 if (LOG.isDebugEnabled()) { 2678 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLUSHED_CACHES_1, Integer.valueOf(event.getType()))); 2679 } 2680 } 2681 2682 /** 2683 * Creates the backup folders for the given export folder and deletes the oldest if the maximum number is reached.<p> 2684 * 2685 * @param staticExport folder for which a new backup folder has to be created 2686 * @param exportPath export path to create backup path out of it 2687 * @param exportBackups number of maximum 2688 * @param ruleBackupExtension extension for rule based backups 2689 */ 2690 protected void createExportBackupFolders( 2691 File staticExport, 2692 String exportPath, 2693 int exportBackups, 2694 String ruleBackupExtension) { 2695 2696 if (staticExport.exists()) { 2697 String backupFolderName = exportPath.substring(0, exportPath.lastIndexOf(File.separator) + 1); 2698 if (ruleBackupExtension != null) { 2699 backupFolderName = backupFolderName + EXPORT_BACKUP_FOLDER_NAME + ruleBackupExtension; 2700 } else { 2701 backupFolderName = backupFolderName + EXPORT_BACKUP_FOLDER_NAME; 2702 } 2703 for (int i = exportBackups; i > 0; i--) { 2704 File staticExportBackupOld = new File(backupFolderName + Integer.valueOf(i).toString()); 2705 if (staticExportBackupOld.exists()) { 2706 if ((i + 1) > exportBackups) { 2707 // delete folder if it is the last backup folder 2708 CmsFileUtil.purgeDirectory(staticExportBackupOld); 2709 } else { 2710 // set backup folder to the next backup folder name 2711 staticExportBackupOld.renameTo(new File(backupFolderName + Integer.valueOf(i + 1).toString())); 2712 } 2713 } 2714 // old export folder rename to first backup folder 2715 if (i == 1) { 2716 staticExport.renameTo(staticExportBackupOld); 2717 } 2718 } 2719 2720 // if no backups will be stored the old export folder has to be deleted 2721 if (exportBackups == 0) { 2722 CmsFileUtil.purgeDirectory(staticExport); 2723 } 2724 } 2725 } 2726 2727 /** 2728 * Creates the parent folder for a exported resource in the RFS.<p> 2729 * 2730 * @param exportPath the path to export the file 2731 * @param rfsName the rfs name of the resource 2732 * 2733 * @throws CmsException if the folder could not be created 2734 */ 2735 protected void createExportFolder(String exportPath, String rfsName) throws CmsException { 2736 2737 String exportFolderName = CmsFileUtil.normalizePath(exportPath + CmsResource.getFolderPath(rfsName)); 2738 File exportFolder = new File(exportFolderName); 2739 if (!exportFolder.exists()) { 2740 // in case of concurrent requests to create this folder, check the folder existence again 2741 if (!exportFolder.mkdirs() && !exportFolder.exists()) { 2742 throw new CmsStaticExportException(Messages.get().container(Messages.ERR_CREATE_FOLDER_1, rfsName)); 2743 } 2744 } 2745 } 2746 2747 /** 2748 * Returns the cacheExportLinks.<p> 2749 * 2750 * @return the cacheExportLinks 2751 */ 2752 protected Map<String, Boolean> getCacheExportLinks() { 2753 2754 return m_cacheExportLinks; 2755 } 2756 2757 /** 2758 * Returns the cacheSecureLinks.<p> 2759 * 2760 * @return the cacheSecureLinks 2761 */ 2762 protected Map<String, String> getCacheSecureLinks() { 2763 2764 return m_cacheSecureLinks; 2765 } 2766 2767 /** 2768 * Returns the export data for a requested resource, if null is returned no export is required.<p> 2769 * 2770 * @param cms an initialized cms context (should be initialized with the "Export" user only) 2771 * @param uri the uri, ie RFS name of the requested resource, with or without the 'export' prefix 2772 * 2773 * @return the export data for the request, if null is returned no export is required 2774 */ 2775 protected CmsStaticExportData getRfsExportData(CmsObject cms, String uri) { 2776 2777 // cut export prefix from name 2778 String rfsName = uri.substring(getRfsPrefixForRfsName(uri).length()); 2779 2780 // check if we have the result already in the cache 2781 CmsStaticExportData data = null; 2782 String siteRoot = OpenCms.getSiteManager().getSiteRoot(cms.getRequestContext().getSiteRoot()); 2783 if (siteRoot != null) { 2784 data = m_cacheExportUris.get(siteRoot + ":" + rfsName); 2785 } else { 2786 data = m_cacheExportUris.get(rfsName); 2787 } 2788 2789 if (data == null) { 2790 // export uri not in cache, must look up the file in the VFS 2791 try { 2792 data = getVfsNameInternal(cms, rfsName); 2793 } catch (CmsVfsResourceNotFoundException e) { 2794 // could happen but is the expected behavior because 2795 // the accoring vfs resource for the given rfsname could not be found 2796 // maybe the rfsname has parameters set -> go on 2797 } 2798 } 2799 2800 if (data == null) { 2801 // it could be a translated resourcename with parameters, 2802 // so make a lookup in the published resources table 2803 try { 2804 String parameters = cms.readStaticExportPublishedResourceParameters(rfsName); 2805 // there was a match in the db table, so get the StaticExportData 2806 if (CmsStringUtil.isNotEmpty(parameters)) { 2807 // get the rfs base string without the parameter hashcode 2808 String rfsBaseName = rfsName.substring(0, rfsName.lastIndexOf('_')); 2809 if (rfsBaseName.endsWith(EXPORT_DEFAULT_FILE)) { 2810 rfsBaseName = rfsBaseName.substring(0, rfsBaseName.length() - EXPORT_DEFAULT_FILE.length()); 2811 } 2812 // get the vfs base name, which is later used to read the resource in the vfs 2813 data = getVfsNameInternal(cms, rfsBaseName); 2814 if (data != null) { 2815 data.setParameters(parameters); 2816 } 2817 } 2818 } catch (CmsVfsResourceNotFoundException e) { 2819 if (LOG.isDebugEnabled()) { 2820 LOG.debug( 2821 Messages.get().getBundle().key( 2822 Messages.LOG_NO_INTERNAL_VFS_RESOURCE_FOUND_1, 2823 new String[] {rfsName})); 2824 } 2825 } catch (CmsException e) { 2826 // ignore, resource does not exist 2827 if (LOG.isWarnEnabled()) { 2828 LOG.warn( 2829 Messages.get().getBundle().key(Messages.ERR_EXPORT_FILE_FAILED_1, new String[] {rfsName}), 2830 e); 2831 } 2832 } 2833 } 2834 2835 if (data == null) { 2836 // no export data found 2837 data = new CmsStaticExportData(CACHEVALUE_404, rfsName, null, null); 2838 } 2839 2840 if (data.getResource() != null) { 2841 siteRoot = OpenCms.getSiteManager().getSiteRoot(data.getResource().getRootPath()); 2842 } 2843 if (siteRoot != null) { 2844 m_cacheExportUris.put(siteRoot + ":" + rfsName, data); 2845 } else { 2846 m_cacheExportUris.put(rfsName, data); 2847 } 2848 2849 // this object comparison is safe, see caller method 2850 if (data.getVfsName() != CACHEVALUE_404) { 2851 if (data.getResource().isFolder() && !CmsResource.isFolder(rfsName)) { 2852 // be sure that folders are folders! 2853 rfsName += "/"; 2854 } 2855 data.setRfsName(rfsName); 2856 // this uri can be exported 2857 return data; 2858 } 2859 // this uri can not be exported 2860 return null; 2861 } 2862 2863 /** 2864 * Returns the rfs name for a given vfs name with consideration of the export name.<p> 2865 * 2866 * @param cms the cms obejct 2867 * @param vfsName the the name of the vfs resource 2868 * 2869 * @return the rfs name for a given vfs name with consideration of the export name 2870 */ 2871 protected String getRfsNameWithExportName(CmsObject cms, String vfsName) { 2872 2873 String rfsName = vfsName; 2874 2875 try { 2876 // check if the resource folder (or a parent folder) has the "exportname" property set 2877 String name = CmsResource.getName(vfsName).replaceAll("/$", ""); 2878 CmsUUID detailId = cms.readIdForUrlName(name); 2879 String propertyReadPath; 2880 if (detailId == null) { 2881 propertyReadPath = CmsResource.getFolderPath(rfsName); 2882 } else { 2883 propertyReadPath = CmsResource.getFolderPath(rfsName.replaceAll("/$", "")); 2884 } 2885 CmsProperty exportNameProperty = cms.readPropertyObject( 2886 propertyReadPath, 2887 CmsPropertyDefinition.PROPERTY_EXPORTNAME, 2888 true); 2889 2890 if (exportNameProperty.isNullProperty()) { 2891 // if "exportname" is not set we must add the site root 2892 rfsName = cms.getRequestContext().addSiteRoot(rfsName); 2893 } else { 2894 // "exportname" property is set 2895 String exportname = normalizeExportName(exportNameProperty.getValue(), propertyReadPath); 2896 if (exportname == null) { 2897 // revert to the default behavior 2898 rfsName = cms.getRequestContext().addSiteRoot(rfsName); 2899 } else { 2900 String value = null; 2901 boolean cont; 2902 String resourceName = rfsName; // resourceName can be the detail page URI 2903 do { 2904 // find out where the export name was set, to replace these parent folders in the RFS name 2905 try { 2906 CmsProperty prop = cms.readPropertyObject( 2907 resourceName, 2908 CmsPropertyDefinition.PROPERTY_EXPORTNAME, 2909 false); 2910 if (prop.isIdentical(exportNameProperty)) { 2911 // look for the right position in path 2912 value = prop.getValue(); 2913 } 2914 cont = (value == null) && (resourceName.length() > 1); 2915 } catch (CmsVfsResourceNotFoundException e) { 2916 // this is for publishing deleted resources 2917 cont = (resourceName.length() > 1); 2918 } catch (CmsSecurityException se) { 2919 // a security exception (probably no read permission) we return the current result 2920 cont = false; 2921 } 2922 if (cont) { 2923 resourceName = CmsResource.getParentFolder(resourceName); 2924 } 2925 } while (cont); 2926 rfsName = exportname + rfsName.substring(resourceName.length()); 2927 } 2928 } 2929 } catch (CmsException e) { 2930 if (LOG.isDebugEnabled()) { 2931 LOG.debug(e.getLocalizedMessage(), e); 2932 } 2933 // ignore exception, return vfsName as rfsName 2934 rfsName = vfsName; 2935 } 2936 return rfsName; 2937 } 2938 2939 /** 2940 * Returns the longest rfs prefix matching a given already translated rfs name.<p> 2941 * 2942 * @param rfsName the rfs name 2943 * 2944 * @return its rfs prefix 2945 * 2946 * @see #getRfsPrefix(String) 2947 */ 2948 protected String getRfsPrefixForRfsName(String rfsName) { 2949 2950 String retVal = ""; 2951 // default case 2952 if (rfsName.startsWith(m_rfsPrefix + "/")) { 2953 retVal = m_rfsPrefix; 2954 } 2955 // additional rules 2956 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 2957 while (it.hasNext()) { 2958 CmsStaticExportRfsRule rule = it.next(); 2959 String rfsPrefix = rule.getRfsPrefix(); 2960 if (rfsName.startsWith(rfsPrefix + "/") && (retVal.length() < rfsPrefix.length())) { 2961 retVal = rfsPrefix; 2962 } 2963 } 2964 return retVal; 2965 } 2966 2967 /** 2968 * Substitutes the ${CONTEXT_NAME} and ${SERVLET_NAME} in a path with the real values.<p> 2969 * 2970 * @param path the path to substitute 2971 * @return path with real context values 2972 */ 2973 protected String insertContextStrings(String path) { 2974 2975 // create a new macro resolver 2976 CmsMacroResolver resolver = CmsMacroResolver.newInstance(); 2977 2978 // add special mappings for macros 2979 resolver.addMacro("CONTEXT_NAME", OpenCms.getSystemInfo().getContextPath()); 2980 resolver.addMacro("SERVLET_NAME", OpenCms.getSystemInfo().getServletPath()); 2981 2982 // resolve the macros 2983 return resolver.resolveMacros(path); 2984 } 2985 2986 /** 2987 * Returns true if the rfs Name match against any of the defined export urls.<p> 2988 * 2989 * @param rfsName the rfs Name to validate 2990 * 2991 * @return true if the rfs Name match against any of the defined export urls 2992 */ 2993 protected boolean isValidRfsName(String rfsName) { 2994 2995 if (rfsName != null) { 2996 // default case 2997 if (rfsName.startsWith(m_rfsPrefix + "/")) { 2998 return true; 2999 } 3000 // additional rules 3001 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 3002 while (it.hasNext()) { 3003 CmsStaticExportRfsRule rule = it.next(); 3004 String rfsPrefix = rule.getRfsPrefix() + "/"; 3005 if (rfsName.startsWith(rfsPrefix)) { 3006 return true; 3007 } 3008 } 3009 } 3010 return false; 3011 } 3012 3013 /** 3014 * Checks if a String is a valid URL.<p> 3015 * 3016 * @param inputString The String to check can be <code>null</code> 3017 * 3018 * @return <code>true</code> if the String is not <code>null</code> and a valid URL 3019 */ 3020 protected boolean isValidURL(String inputString) { 3021 3022 boolean isValid = false; 3023 try { 3024 if (inputString != null) { 3025 URL tempURL = new URL(inputString); 3026 isValid = (tempURL.getProtocol() != null); 3027 } 3028 } catch (MalformedURLException mue) { 3029 // ignore because it is not harmful 3030 } 3031 return isValid; 3032 } 3033 3034 /** 3035 * Returns a normalized export path.<p> 3036 * 3037 * Replacing macros, normalizing the path and taking care of relative paths.<p> 3038 * 3039 * @param exportPath the export path to normalize 3040 * 3041 * @return the normalized export path 3042 */ 3043 protected String normalizeExportPath(String exportPath) { 3044 3045 String result = insertContextStrings(exportPath); 3046 result = OpenCms.getSystemInfo().getAbsoluteRfsPathRelativeToWebApplication(result); 3047 if (result.endsWith(File.separator)) { 3048 // ensure export path does NOT end with a File.separator 3049 result = result.substring(0, result.length() - 1); 3050 } 3051 return result; 3052 } 3053 3054 /** 3055 * Returns a normalized rfs prefix.<p> 3056 * 3057 * Replacing macros and normalizing the path.<p> 3058 * 3059 * @param rfsPrefix the prefix to normalize 3060 * 3061 * @return the normalized rfs prefix 3062 */ 3063 protected String normalizeRfsPrefix(String rfsPrefix) { 3064 3065 String result = insertContextStrings(rfsPrefix); 3066 if (!isValidURL(result)) { 3067 result = CmsFileUtil.normalizePath(result, '/'); 3068 } 3069 if (CmsResource.isFolder(result)) { 3070 // ensure prefix does NOT end with a folder '/' 3071 result = result.substring(0, result.length() - 1); 3072 } 3073 return result; 3074 } 3075 3076 /** 3077 * Reads the resource with the given URI.<p> 3078 * 3079 * @param cms the current CMS context 3080 * @param uri the URI to check 3081 * 3082 * @return the resource export data 3083 * 3084 * @throws CmsException if soemthing goes wrong 3085 */ 3086 protected CmsStaticExportData readResource(CmsObject cms, String uri) throws CmsException { 3087 3088 CmsResource resource = null; 3089 boolean isDetailPage = false; 3090 3091 try { 3092 resource = cms.readResource(uri); 3093 } catch (CmsVfsResourceNotFoundException e) { 3094 3095 String urlName = CmsResource.getName(uri).replaceAll("/$", ""); 3096 CmsUUID id = cms.readIdForUrlName(urlName); 3097 if (id == null) { 3098 throw e; 3099 } 3100 resource = cms.readResource(id); 3101 isDetailPage = true; 3102 3103 //String parent = CmsResource.getParentFolder(uri); 3104 //resource = cms.readDefaultFile(parent); 3105 } 3106 CmsStaticExportData result = new CmsStaticExportData(uri, null, resource, null); 3107 result.setIsDetailPage(isDetailPage); 3108 return result; 3109 } 3110 3111 /** 3112 * Scrubs all the "export" folders.<p> 3113 * 3114 * @param report an I_CmsReport instance to print output message, or null to write messages to the log file 3115 */ 3116 protected void scrubExportFolders(I_CmsReport report) { 3117 3118 if (report != null) { 3119 report.println( 3120 Messages.get().container(Messages.RPT_DELETING_EXPORT_FOLDERS_BEGIN_0), 3121 I_CmsReport.FORMAT_HEADLINE); 3122 } 3123 synchronized (m_lockScrubExportFolders) { 3124 int count = 0; 3125 Integer size = Integer.valueOf(m_rfsRules.size() + 1); 3126 // default case 3127 String exportFolderName = CmsFileUtil.normalizePath(m_staticExportPath + '/'); 3128 try { 3129 File exportFolder = new File(exportFolderName); 3130 // check if export file exists, if so delete it 3131 if (exportFolder.exists() && exportFolder.canWrite()) { 3132 CmsFileUtil.purgeDirectory(exportFolder); 3133 } 3134 count++; 3135 if (report != null) { 3136 report.println( 3137 Messages.get().container( 3138 Messages.RPT_DELETE_EXPORT_FOLDER_3, 3139 Integer.valueOf(count), 3140 size, 3141 exportFolderName), 3142 I_CmsReport.FORMAT_NOTE); 3143 } else { 3144 // write log message 3145 if (LOG.isInfoEnabled()) { 3146 LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MAIN_SE_FOLDER_1, exportFolderName)); 3147 } 3148 } 3149 } catch (Throwable t) { 3150 // ignore, nothing to do about the 3151 if (LOG.isWarnEnabled()) { 3152 LOG.warn( 3153 Messages.get().getBundle().key(Messages.LOG_FOLDER_DELETION_FAILED_1, exportFolderName), 3154 t); 3155 } 3156 } 3157 // iterate over the rules 3158 Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); 3159 while (it.hasNext()) { 3160 CmsStaticExportRfsRule rule = it.next(); 3161 exportFolderName = CmsFileUtil.normalizePath(rule.getExportPath() + '/'); 3162 try { 3163 File exportFolder = new File(exportFolderName); 3164 // check if export file exists, if so delete it 3165 if (exportFolder.exists() && exportFolder.canWrite()) { 3166 CmsFileUtil.purgeDirectory(exportFolder); 3167 } 3168 count++; 3169 if (report != null) { 3170 report.println( 3171 Messages.get().container( 3172 Messages.RPT_DELETE_EXPORT_FOLDER_3, 3173 Integer.valueOf(count), 3174 size, 3175 exportFolderName), 3176 I_CmsReport.FORMAT_NOTE); 3177 } else { 3178 // write log message 3179 if (LOG.isInfoEnabled()) { 3180 LOG.info( 3181 Messages.get().getBundle().key(Messages.LOG_DEL_MAIN_SE_FOLDER_1, exportFolderName)); 3182 } 3183 } 3184 } catch (Throwable t) { 3185 // ignore, nothing to do about the 3186 if (LOG.isWarnEnabled()) { 3187 LOG.warn( 3188 Messages.get().getBundle().key(Messages.LOG_FOLDER_DELETION_FAILED_1, exportFolderName), 3189 t); 3190 } 3191 } 3192 } 3193 } 3194 if (report != null) { 3195 report.println( 3196 Messages.get().container(Messages.RPT_DELETING_EXPORT_FOLDERS_END_0), 3197 I_CmsReport.FORMAT_HEADLINE); 3198 } 3199 } 3200 3201 /** 3202 * Handles a streamed static export through the configured stored content delivery handler if possible.<p> 3203 * 3204 * @param loader the resource loader 3205 * @param cms the CMS context 3206 * @param exportResource the resource to export 3207 * @param req the current request 3208 * @param res the current response 3209 * @param exportFileName the target export file name 3210 * 3211 * @return <code>true</code> if the export has been handled without writing the local export file 3212 * 3213 * @throws CmsException if something goes wrong 3214 */ 3215 protected boolean tryHandleConfiguredStoredContentDeliveryExport( 3216 I_CmsStaticExportStreamLoader loader, 3217 CmsObject cms, 3218 CmsResource exportResource, 3219 HttpServletRequest req, 3220 HttpServletResponse res, 3221 String exportFileName) 3222 throws CmsException { 3223 3224 CmsStoredContentDeliveryConfiguration configuration = getStoredContentDeliveryConfiguration(); 3225 if (!configuration.isEnabled()) { 3226 return false; 3227 } 3228 return m_storedContentDeliverySupport.handle( 3229 this, 3230 configuration, 3231 loader, 3232 cms, 3233 exportResource, 3234 req, 3235 res, 3236 exportFileName); 3237 } 3238 3239 /** 3240 * Writes a resource to the given export path with the given rfs name and the given content.<p> 3241 * 3242 * @param req the current request 3243 * @param exportPath the path to export the resource 3244 * @param rfsName the rfs name 3245 * @param resource the resource 3246 * @param content the content 3247 * 3248 * @throws CmsException if something goes wrong 3249 */ 3250 protected void writeResource( 3251 HttpServletRequest req, 3252 String exportPath, 3253 String rfsName, 3254 CmsResource resource, 3255 byte[] content) 3256 throws CmsException { 3257 3258 String exportFileName = CmsFileUtil.normalizePath(exportPath + rfsName); 3259 3260 // make sure all required parent folder exist 3261 createExportFolder(exportPath, rfsName); 3262 // generate export file instance and output stream 3263 File exportFile = new File(exportFileName); 3264 // write new exported file content 3265 try { 3266 FileOutputStream exportStream = new FileOutputStream(exportFile); 3267 exportStream.write(content); 3268 exportStream.close(); 3269 3270 // log export success 3271 logStaticExport(resource, exportFileName); 3272 3273 } catch (Throwable t) { 3274 throw new CmsStaticExportException( 3275 Messages.get().container(Messages.ERR_OUTPUT_STREAM_1, exportFileName), 3276 t); 3277 } 3278 setExportFileLastModified(req, exportFile, resource); 3279 } 3280 3281 /** 3282 * Writes a resource to the given export path by streaming the loader output.<p> 3283 * 3284 * @param loader the resource loader 3285 * @param cms the CMS context 3286 * @param exportResource the resource to export 3287 * @param req the current request 3288 * @param res the current response 3289 * @param exportPath the path to export the resource 3290 * @param rfsName the rfs name 3291 * @param logResource the resource used for logging 3292 * 3293 * @throws CmsException if something goes wrong 3294 */ 3295 protected void writeStreamedResource( 3296 I_CmsStaticExportStreamLoader loader, 3297 CmsObject cms, 3298 CmsResource exportResource, 3299 HttpServletRequest req, 3300 HttpServletResponse res, 3301 String exportPath, 3302 String rfsName, 3303 CmsResource logResource) 3304 throws CmsException { 3305 3306 String exportFileName = CmsFileUtil.normalizePath(exportPath + rfsName); 3307 if (tryHandleConfiguredStoredContentDeliveryExport(loader, cms, exportResource, req, res, exportFileName)) { 3308 return; 3309 } 3310 createExportFolder(exportPath, rfsName); 3311 File exportFile = new File(exportFileName); 3312 try { 3313 FileOutputStream exportStream = new FileOutputStream(exportFile); 3314 try { 3315 loader.exportTo(cms, exportResource, req, res, exportStream); 3316 } finally { 3317 exportStream.close(); 3318 } 3319 logStaticExport(logResource, exportFileName); 3320 } catch (Throwable t) { 3321 throw new CmsStaticExportException( 3322 Messages.get().container(Messages.ERR_OUTPUT_STREAM_1, exportFileName), 3323 t); 3324 } 3325 setExportFileLastModified(req, exportFile, logResource); 3326 } 3327 3328 /** 3329 * Returns the map of vfs exportnames with exportname as key and the vfs folder path as value.<p> 3330 * 3331 * @return the map of vfs exportnames with exportname as key and the vfs folder path as value 3332 */ 3333 private Map<CmsExportname, String> computeVfsExportnames() { 3334 3335 if (LOG.isDebugEnabled()) { 3336 LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_START_0)); 3337 } 3338 3339 CmsSiteManagerImpl sm = OpenCms.getSiteManager(); 3340 3341 List<CmsResource> resources; 3342 CmsObject cms = null; 3343 try { 3344 // this will always be in the root site 3345 cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); 3346 resources = cms.readResourcesWithProperty(CmsPropertyDefinition.PROPERTY_EXPORTNAME); 3347 3348 synchronized (m_lockSetExportnames) { 3349 Map<CmsExportname, String> exportnameResources = new HashMap<CmsExportname, String>(); 3350 for (int i = 0, n = resources.size(); i < n; i++) { 3351 CmsResource res = resources.get(i); 3352 try { 3353 String foldername = res.getRootPath(); 3354 String exportname = cms.readPropertyObject( 3355 foldername, 3356 CmsPropertyDefinition.PROPERTY_EXPORTNAME, 3357 false).getValue(); 3358 CmsSite site = sm.getSiteForRootPath(foldername); 3359 exportname = normalizeExportName(exportname, foldername); 3360 if (exportname != null) { 3361 // export name has to be system-wide unique 3362 // the folder name is a root path 3363 exportnameResources.put(new CmsExportname(exportname, site), foldername); 3364 } 3365 } catch (CmsException e) { 3366 // should never happen, folder will not be added 3367 LOG.error(e.getLocalizedMessage(), e); 3368 } 3369 } 3370 return Collections.unmodifiableMap(exportnameResources); 3371 } 3372 } catch (CmsException e) { 3373 // should never happen, no resources will be added at all 3374 LOG.error(e.getLocalizedMessage(), e); 3375 return Collections.emptyMap(); 3376 } 3377 } 3378 3379 /** 3380 * Delivers an on-demand static export response without writing an RFS export copy.<p> 3381 * 3382 * @param loader the resource loader 3383 * @param cms the initialized CMS context 3384 * @param resource the resource to export 3385 * @param file the file for non-streaming loaders 3386 * @param req the current request 3387 * @param res the current response 3388 * @param contentType the response content type 3389 * 3390 * @return the HTTP response status 3391 * 3392 * @throws CmsException if accessing the VFS or storage fails 3393 * @throws IOException if writing the response fails 3394 * @throws ServletException if the loader fails to create the response 3395 */ 3396 private int exportToSharedCache( 3397 I_CmsResourceLoader loader, 3398 CmsObject cms, 3399 CmsResource resource, 3400 CmsFile file, 3401 HttpServletRequest req, 3402 CmsStaticExportResponseWrapper res, 3403 String contentType) 3404 throws CmsException, IOException, ServletException { 3405 3406 for (String header : m_exportHeaders) { 3407 String[] parts = CmsStringUtil.splitAsArray(header, ':'); 3408 if (parts.length == 2) { 3409 res.setHeader(parts[0], parts[1]); 3410 } 3411 } 3412 setSharedCacheHeaders(res, contentType); 3413 boolean exported; 3414 if (loader instanceof I_CmsStaticExportStreamLoader) { 3415 loader.load(cms, resource, req, res); 3416 exported = true; 3417 } else { 3418 exported = loader.export(cms, new CmsFile(file), req, res) != null; 3419 } 3420 if (!exported) { 3421 return HttpServletResponse.SC_NOT_MODIFIED; 3422 } 3423 int status = res.getStatus(); 3424 return status < 0 ? HttpServletResponse.SC_OK : status; 3425 } 3426 3427 /** 3428 * Logs a successful static export.<p> 3429 * 3430 * @param resource the exported resource 3431 * @param exportFileName the export file name 3432 */ 3433 private void logStaticExport(CmsResource resource, String exportFileName) { 3434 3435 if (LOG.isInfoEnabled()) { 3436 LOG.info( 3437 Messages.get().getBundle().key(Messages.LOG_STATIC_EXPORTED_2, resource.getRootPath(), exportFileName)); 3438 } 3439 } 3440 3441 /** 3442 * Normalizes and validates an export name.<p> 3443 * 3444 * @param exportName the export name 3445 * @param resourcePath the path of the resource on which the export name is defined 3446 * 3447 * @return the normalized export name, or <code>null</code> if the export name is invalid 3448 */ 3449 private String normalizeExportName(String exportName, String resourcePath) { 3450 3451 if (CmsStringUtil.isEmpty(exportName)) { 3452 LOG.warn("Invalid export name '" + exportName + "' at " + resourcePath); 3453 return null; 3454 } 3455 if (exportName.charAt(0) != '/') { 3456 exportName = '/' + exportName; 3457 } 3458 if (exportName.charAt(exportName.length() - 1) != '/') { 3459 exportName = exportName + '/'; 3460 } 3461 if (PATTERN_PARENT_PATH_COMPONENT.matcher(exportName).find()) { 3462 LOG.warn("Invalid export name '" + exportName + "' at " + resourcePath); 3463 return null; 3464 } 3465 return exportName; 3466 } 3467 3468 /** 3469 * Updates the exported file with the modification date from the request or resource.<p> 3470 * 3471 * @param req the current request 3472 * @param exportFile the exported file 3473 * @param resource the exported resource 3474 */ 3475 private void setExportFileLastModified(HttpServletRequest req, File exportFile, CmsResource resource) { 3476 3477 if (req != null) { 3478 Long dateLastModified = (Long)req.getAttribute(CmsRequestUtil.HEADER_OPENCMS_EXPORT); 3479 if ((dateLastModified != null) && (dateLastModified.longValue() != -1)) { 3480 long lastModified = (dateLastModified.longValue() / 1000) * 1000; 3481 exportFile.setLastModified(lastModified); 3482 if (LOG.isDebugEnabled()) { 3483 LOG.debug( 3484 Messages.get().getBundle().key( 3485 Messages.LOG_SET_LAST_MODIFIED_2, 3486 exportFile.getName(), 3487 Long.valueOf(lastModified))); 3488 } 3489 } 3490 } else { 3491 exportFile.setLastModified((resource.getDateLastModified() / 1000) * 1000); 3492 } 3493 } 3494 3495 /** 3496 * Sets the shared cache response headers for the matching content type policy.<p> 3497 * 3498 * @param res the current response 3499 * @param contentType the response content type 3500 */ 3501 private void setSharedCacheHeaders(CmsStaticExportResponseWrapper res, String contentType) { 3502 3503 CmsSharedCachePolicy policy = m_sharedCacheConfiguration.getCachePolicy(contentType); 3504 if (policy == null) { 3505 return; 3506 } 3507 StringBuilder cacheControl = new StringBuilder(64); 3508 cacheControl.append("public, max-age="); 3509 cacheControl.append(policy.getClientMaxAge()); 3510 cacheControl.append(", s-maxage="); 3511 cacheControl.append(policy.getSharedMaxAge()); 3512 if (policy.getStaleIfError() != CmsSharedCachePolicy.DURATION_UNSET) { 3513 cacheControl.append(", stale-if-error="); 3514 cacheControl.append(policy.getStaleIfError()); 3515 } 3516 res.enforceCacheControl(cacheControl.toString()); 3517 } 3518}