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