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