/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.opencms.staticexport; import org.opencms.ade.detailpage.CmsDetailPageUtil; import org.opencms.ade.detailpage.I_CmsDetailPageFinder; import org.opencms.file.CmsFile; import org.opencms.file.CmsObject; import org.opencms.file.CmsProperty; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsResource; import org.opencms.file.CmsVfsResourceNotFoundException; import org.opencms.file.types.CmsResourceTypeJsp; import org.opencms.i18n.CmsAcceptLanguageHeaderParser; import org.opencms.i18n.CmsI18nInfo; import org.opencms.i18n.CmsLocaleManager; import org.opencms.loader.I_CmsResourceLoader; import org.opencms.main.CmsContextInfo; import org.opencms.main.CmsEvent; import org.opencms.main.CmsException; import org.opencms.main.CmsIllegalArgumentException; import org.opencms.main.CmsLog; import org.opencms.main.CmsSystemInfo; import org.opencms.main.I_CmsEventListener; import org.opencms.main.OpenCms; import org.opencms.report.CmsLogReport; import org.opencms.report.I_CmsReport; import org.opencms.security.CmsSecurityException; import org.opencms.site.CmsSite; import org.opencms.util.CmsCollectionsGenericWrapper; import org.opencms.util.CmsFileUtil; import org.opencms.util.CmsMacroResolver; import org.opencms.util.CmsRequestUtil; import org.opencms.util.CmsStringUtil; import org.opencms.util.CmsUUID; import org.opencms.workplace.CmsWorkplace; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; /** * Provides the functionality to export resources from the OpenCms VFS * to the file system.<p> * * @since 6.0.0 */ public class CmsStaticExportManager implements I_CmsEventListener { /** Name for the default file. */ public static final String DEFAULT_FILE = "index.html"; /** Marker for error message attribute. */ public static final String EXPORT_ATTRIBUTE_ERROR_MESSAGE = "javax.servlet.error.message"; /** Marker for error request uri attribute. */ public static final String EXPORT_ATTRIBUTE_ERROR_REQUEST_URI = "javax.servlet.error.request_uri"; /** Marker for error servlet name attribute. */ public static final String EXPORT_ATTRIBUTE_ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"; /** Marker for error status code attribute. */ public static final String EXPORT_ATTRIBUTE_ERROR_STATUS_CODE = "javax.servlet.error.status_code"; /** Name for the backup folder default name. */ public static final String EXPORT_BACKUP_FOLDER_NAME = "backup"; /** Name for the default work path. */ public static final Integer EXPORT_DEFAULT_BACKUPS = new Integer(0); /** Name for the folder default index file. */ public static final String EXPORT_DEFAULT_FILE = "index_export.html"; /** Name for the default work path. */ public static final String EXPORT_DEFAULT_WORKPATH = CmsSystemInfo.FOLDER_WEBINF + "temp"; /** Flag value for links without parameters. */ public static final int EXPORT_LINK_WITH_PARAMETER = 2; /** Flag value for links without parameters. */ public static final int EXPORT_LINK_WITHOUT_PARAMETER = 1; /** Marker for externally redirected 404 uri's. */ public static final String EXPORT_MARKER = "exporturi"; /** Time given (in seconds) to the static export handler to finish a publish task. */ public static final int HANDLER_FINISH_TIME = 60; /** Cache value to indicate a true 404 error. */ private static final String CACHEVALUE_404 = "?404"; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsStaticExportManager.class); /** HTTP header Accept-Charset. */ private String m_acceptCharsetHeader; /** HTTP header Accept-Language. */ private String m_acceptLanguageHeader; /** Cache for the export links. */ private Map<String, Boolean> m_cacheExportLinks; /** Cache for the export uris. */ private Map<String, CmsStaticExportData> m_cacheExportUris; /** Cache for the online links. */ private Map<String, String> m_cacheOnlineLinks; /** Cache for the secure links. */ private Map<String, Boolean> m_cacheSecureLinks; /** OpenCms default charset header. */ private String m_defaultAcceptCharsetHeader; /** OpenCms default locale header. */ private String m_defaultAcceptLanguageHeader; /** Matcher for selecting those resources which should be part of the static export. */ private CmsExportFolderMatcher m_exportFolderMatcher; /** List of export resources which should be part of the static export. */ private List<String> m_exportFolders; /** The additional http headers for the static export. */ private List<String> m_exportHeaders; /** List of all resources that have the "exportname" property set: <system-wide unique export name, root path>. */ private Map<String, String> m_exportnameResources; /** Indicates if <code>true</code> is the default value for the property "export". */ private boolean m_exportPropertyDefault; /** Indicates if links in the static export should be relative. */ private boolean m_exportRelativeLinks; /** List of export rules. */ private List<CmsStaticExportExportRule> m_exportRules; /** List of export suffixes where the "export" property default is always <code>true</code>. */ private List<String> m_exportSuffixes; /** Temporary variable for reading the xml config file. */ private CmsStaticExportExportRule m_exportTmpRule; /** Export url to send internal requests to. */ private String m_exportUrl; /** Export url with unsubstituted context values. */ private String m_exportUrlConfigured; /** Export url to send internal requests to without http://servername. */ private String m_exportUrlPrefix; /** Boolean value if the export is a full static export. */ private boolean m_fullStaticExport; /** Handler class for static export. */ private I_CmsStaticExportHandler m_handler; /** The configured link substitution handler. */ private I_CmsLinkSubstitutionHandler m_linkSubstitutionHandler; /** Lock object for write access to the {@link #cmsEvent(CmsEvent)} method. */ private Object m_lockCmsEvent; /** Lock object for export folder deletion in {@link #scrubExportFolders(I_CmsReport)}. */ private Object m_lockScrubExportFolders; /** Lock object for write access to the {@link #m_exportnameResources} map in {@link #computeVfsExportnames()}. */ private Object m_lockSetExportnames; /** Indicates if the quick static export for plain resources is enabled. */ private boolean m_quickPlainExport; /** Remote address. */ private String m_remoteAddr; /** Prefix to use for exported files. */ private String m_rfsPrefix; /** Prefix to use for exported files with unsubstituted context values. */ private String m_rfsPrefixConfigured; /** List of configured rfs rules. */ private List<CmsStaticExportRfsRule> m_rfsRules; /** Temporary variable for reading the xml config file. */ private CmsStaticExportRfsRule m_rfsTmpRule; /** The number of backups stored for the export folder. */ private Integer m_staticExportBackups; /** Indicates if the static export is enabled or disabled. */ private boolean m_staticExportEnabled; /** The path to where the static export will be written. */ private String m_staticExportPath; /** The path to where the static export will be written without the complete rfs path. */ private String m_staticExportPathConfigured; /** The path to where the static export will be written during the static export process. */ private String m_staticExportWorkPath; /** The path to where the static export will be written during the static export process without the complete rfs path. */ private String m_staticExportWorkPathConfigured; /** Vfs Name of a resource used to do a "static export required" test. */ private String m_testResource; /** If there are several identical export paths the usage of temporary directories has to be disabled. */ private boolean m_useTempDirs = true; /** Prefix to use for internal OpenCms files. */ private String m_vfsPrefix; /** Prefix to use for internal OpenCms files with unsubstituted context values. */ private String m_vfsPrefixConfigured; /** * Creates a new static export property object.<p> * */ public CmsStaticExportManager() { m_lockCmsEvent = new Object(); m_lockScrubExportFolders = new Object(); m_lockSetExportnames = new Object(); m_exportSuffixes = new ArrayList<String>(); m_exportFolders = new ArrayList<String>(); m_exportHeaders = new ArrayList<String>(); m_rfsRules = new ArrayList<CmsStaticExportRfsRule>(); m_exportRules = new ArrayList<CmsStaticExportExportRule>(); m_exportTmpRule = new CmsStaticExportExportRule("", ""); m_rfsTmpRule = new CmsStaticExportRfsRule("", "", "", "", "", "", null, null); m_fullStaticExport = false; } /** * Returns the real file system name plus the default file name.<p> * * @param rfsName the real file system name to append the default file name to * @param isFolder signals whether the according virtual file system resource is an folder or not * * @return the real file system name plus the default file name */ public String addDefaultFileNameToFolder(String rfsName, boolean isFolder) { StringBuffer name = new StringBuffer(rfsName); if (isFolder) { // vfs folder case name.append(EXPORT_DEFAULT_FILE); } return name.toString(); } /** * Adds a new export rule to the configuration.<p> * * @param name the name of the rule * @param description the description for the rule */ public void addExportRule(String name, String description) { m_exportRules.add(new CmsStaticExportExportRule( name, description, m_exportTmpRule.getModifiedResources(), m_exportTmpRule.getExportResourcePatterns())); m_exportTmpRule = new CmsStaticExportExportRule("", ""); } /** * Adds a regex to the latest export rule.<p> * * @param regex the regex to add */ public void addExportRuleRegex(String regex) { m_exportTmpRule.addModifiedResource(regex); } /** * Adds a export uri to the latest export rule.<p> * * @param exportUri the export uri to add */ public void addExportRuleUri(String exportUri) { m_exportTmpRule.addExportResourcePattern(exportUri); } /** * Adds a new rfs rule to the configuration.<p> * * @param name the name of the rule * @param description the description for the rule * @param source the source regex * @param rfsPrefix the url prefix * @param exportPath the rfs export path * @param exportWorkPath the rfs export work path * @param exportBackups the number of backups * @param useRelativeLinks the relative links value */ public void addRfsRule( String name, String description, String source, String rfsPrefix, String exportPath, String exportWorkPath, String exportBackups, String useRelativeLinks) { if ((m_staticExportPathConfigured != null) && exportPath.equals(m_staticExportPathConfigured)) { m_useTempDirs = false; } Iterator<CmsStaticExportRfsRule> itRules = m_rfsRules.iterator(); while (m_useTempDirs && itRules.hasNext()) { CmsStaticExportRfsRule rule = itRules.next(); if (exportPath.equals(rule.getExportPathConfigured())) { m_useTempDirs = false; } } Boolean relativeLinks = (useRelativeLinks == null ? null : Boolean.valueOf(useRelativeLinks)); Integer backups = (exportBackups == null ? null : Integer.valueOf(exportBackups)); m_rfsRules.add(new CmsStaticExportRfsRule( name, description, source, rfsPrefix, exportPath, exportWorkPath, backups, relativeLinks, m_rfsTmpRule.getRelatedSystemResources())); m_rfsTmpRule = new CmsStaticExportRfsRule("", "", "", "", "", "", null, null); } /** * Adds a regex of related system resources to the latest rfs-rule.<p> * * @param regex the regex to add */ public void addRfsRuleSystemRes(String regex) { m_rfsTmpRule.addRelatedSystemRes(regex); } /** * Caches a calculated online link.<p> * * @param linkName the link * @param vfsName the name of the VFS resource */ public void cacheOnlineLink(String linkName, String vfsName) { m_cacheOnlineLinks.put(linkName, vfsName); } /** * Implements the CmsEvent interface, * the static export properties uses the events to clear * the list of cached keys in case a project is published.<p> * * @param event CmsEvent that has occurred */ public void cmsEvent(CmsEvent event) { if (!isStaticExportEnabled()) { if (LOG.isWarnEnabled()) { LOG.warn(Messages.get().getBundle().key(Messages.LOG_STATIC_EXPORT_DISABLED_0)); } return; } I_CmsReport report = null; Map<String, Object> data = event.getData(); if (data != null) { report = (I_CmsReport)data.get(I_CmsEventListener.KEY_REPORT); } if (report == null) { report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass()); } switch (event.getType()) { case I_CmsEventListener.EVENT_UPDATE_EXPORTS: scrubExportFolders(report); clearCaches(event); break; case I_CmsEventListener.EVENT_PUBLISH_PROJECT: if (data == null) { if (LOG.isErrorEnabled()) { LOG.error(Messages.get().getBundle().key(Messages.ERR_EMPTY_EVENT_DATA_0)); } return; } // event data contains a list of the published resources CmsUUID publishHistoryId = new CmsUUID((String)data.get(I_CmsEventListener.KEY_PUBLISHID)); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_EVENT_PUBLISH_PROJECT_1, publishHistoryId)); } synchronized (m_lockCmsEvent) { getHandler().performEventPublishProject(publishHistoryId, report); } clearCaches(event); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_EVENT_PUBLISH_PROJECT_FINISHED_1, publishHistoryId)); } break; case I_CmsEventListener.EVENT_CLEAR_CACHES: clearCaches(event); break; default: // no operation } } /** * Exports the requested uri and at the same time writes the uri to the response output stream * if required.<p> * * @param req the current request * @param res the current response * @param cms an initialised cms context (should be initialised with the "Guest" user only) * @param data the static export data set * * @return status code of the export operation, status codes are the same as http status codes (200,303,304) * * @throws CmsException in case of errors accessing the VFS * @throws ServletException in case of errors accessing the servlet * @throws IOException in case of errors writing to the export output stream * @throws CmsStaticExportException if static export is disabled */ public int export(HttpServletRequest req, HttpServletResponse res, CmsObject cms, CmsStaticExportData data) throws CmsException, IOException, ServletException, CmsStaticExportException { CmsResource resource = data.getResource(); String vfsName = data.getVfsName(); String rfsName; if (data.getParameters() != null) { rfsName = data.getRfsName(); } else { rfsName = addDefaultFileNameToFolder(data.getRfsName(), resource.isFolder()); } // cut the site root from the vfsName and switch to the correct site String siteRoot = OpenCms.getSiteManager().getSiteRoot(vfsName); CmsI18nInfo i18nInfo = OpenCms.getLocaleManager().getI18nInfo( req, cms.getRequestContext().getCurrentUser(), cms.getRequestContext().getCurrentProject(), vfsName); String remoteAddr = m_remoteAddr; if (remoteAddr == null) { remoteAddr = CmsContextInfo.LOCALHOST; } if (siteRoot != null) { vfsName = vfsName.substring(siteRoot.length()); } else { siteRoot = "/"; } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_STATIC_EXPORT_SITE_ROOT_2, siteRoot, vfsName)); } CmsContextInfo contextInfo = new CmsContextInfo( cms.getRequestContext().getCurrentUser(), cms.getRequestContext().getCurrentProject(), vfsName, siteRoot, i18nInfo.getLocale(), i18nInfo.getEncoding(), remoteAddr, CmsContextInfo.CURRENT_TIME, cms.getRequestContext().getOuFqn()); CmsObject exportCms = OpenCms.initCmsObject(null, contextInfo); // only export those resources where the export property is set if (!isExportLink(exportCms, exportCms.getRequestContext().removeSiteRoot(data.getVfsName()))) { // the resource was not used for export, so return HttpServletResponse.SC_SEE_OTHER // as a signal for not exported resource return HttpServletResponse.SC_SEE_OTHER; } // this flag signals if the export method is used for "on demand" or "after publish". // if no request and result stream are available, it was called during "export on publish" boolean exportOnDemand = ((req != null) && (res != null)); CmsStaticExportResponseWrapper wrapRes = null; if (res != null) { wrapRes = new CmsStaticExportResponseWrapper(res); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_SE_RESOURCE_START_1, data)); } CmsFile file = exportCms.readFile(OpenCms.initResource(exportCms, vfsName, req, wrapRes)); vfsName = exportCms.getSitePath(file); // check loader id for resource I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(file); if ((loader == null) || (!loader.isStaticExportEnabled())) { Object[] arguments = new Object[] {vfsName, new Integer(file.getTypeId())}; throw new CmsStaticExportException(Messages.get().container(Messages.ERR_EXPORT_NOT_SUPPORTED_2, arguments)); } // ensure we have exactly the same setup as if called "the usual way" // we only have to do this in case of the static export on demand if (exportOnDemand) { String mimetype = OpenCms.getResourceManager().getMimeType( file.getName(), exportCms.getRequestContext().getEncoding()); if (wrapRes != null) { wrapRes.setContentType(mimetype); } exportCms.getRequestContext().setUri(vfsName); } // do the export int status = -1; List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(exportCms, vfsName); boolean exported = false; boolean matched = false; // iterate over all rules Iterator<CmsStaticExportRfsRule> it = getRfsRules().iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); // normal case boolean export = rule.getSource().matcher(siteRoot + vfsName).matches(); matched |= export; // system folder case export |= (vfsName.startsWith(CmsWorkplace.VFS_PATH_SYSTEM) && rule.match(vfsName)); if (export) { // the resource has to exported for this rule CmsObject locCms = exportCms; Locale locale = CmsLocaleManager.getLocale(rule.getName()); if (locales.contains(locale)) { // if the locale is in the default locales for the resource // so adjust the locale to use for exporting CmsContextInfo ctxInfo = new CmsContextInfo(exportCms.getRequestContext()); ctxInfo.setLocale(locale); locCms = OpenCms.initCmsObject(exportCms, ctxInfo); } // read the content in the matching locale byte[] content = loader.export(locCms, file, req, wrapRes); if (content != null) { // write to rfs exported = true; String locRfsName = rfsName; if (locales.contains(locale)) { locRfsName = rule.getLocalizedRfsName(rfsName, "/"); } writeResource(req, rule.getExportPath(), locRfsName, resource, content); } } } if (!matched) { // no rule matched String exportPath = getExportPath(siteRoot + vfsName); byte[] content = loader.export(exportCms, file, req, wrapRes); if (content != null) { exported = true; writeResource(req, exportPath, rfsName, resource, content); } } if (exported) { // get the wrapper status that was set status = (wrapRes != null) ? wrapRes.getStatus() : -1; if (status < 0) { // the status was not set, assume everything is o.k. status = HttpServletResponse.SC_OK; } } else { // the resource was not written because it was not modified. // set the status to not modified status = HttpServletResponse.SC_NOT_MODIFIED; } return status; } /** * Starts a complete static export of all resources.<p> * * @param purgeFirst flag to delete all resources in the export folder of the rfs * @param report an I_CmsReport instance to print output message, or null to write messages to the log file * * @throws CmsException in case of errors accessing the VFS * @throws IOException in case of errors writing to the export output stream * @throws ServletException in case of errors accessing the servlet */ public synchronized void exportFullStaticRender(boolean purgeFirst, I_CmsReport report) throws CmsException, IOException, ServletException { // set member to true to get temporary export paths for rules m_fullStaticExport = true; // save the real export path String staticExportPathStore = m_staticExportPath; if (m_useTempDirs) { // set the export path to the export work path m_staticExportPath = m_staticExportWorkPath; } // delete all old exports if the purgeFirst flag is set if (purgeFirst) { Map<String, Object> eventData = new HashMap<String, Object>(); eventData.put(I_CmsEventListener.KEY_REPORT, report); CmsEvent clearCacheEvent = new CmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, eventData); OpenCms.fireCmsEvent(clearCacheEvent); scrubExportFolders(report); // this will always use the root site CmsObject cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); cms.deleteAllStaticExportPublishedResources(EXPORT_LINK_WITHOUT_PARAMETER); cms.deleteAllStaticExportPublishedResources(EXPORT_LINK_WITH_PARAMETER); } // do the export CmsAfterPublishStaticExportHandler handler = new CmsAfterPublishStaticExportHandler(); // export everything handler.doExportAfterPublish(null, report); // set export path to the original one m_staticExportPath = staticExportPathStore; // set member to false for further exports m_fullStaticExport = false; // check if report contents no errors if (m_useTempDirs && !report.hasError()) { // backup old export folders for default export File staticExport = new File(m_staticExportPath); createExportBackupFolders(staticExport, m_staticExportPath, getExportBackups().intValue(), null); // change the name of the used temporary export folder to the original default export path File staticExportWork = new File(m_staticExportWorkPath); staticExportWork.renameTo(new File(m_staticExportPath)); // backup old export folders of rule based exports Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); File staticExportRule = new File(rule.getExportPath()); File staticExportWorkRule = new File(rule.getExportWorkPath()); // only backup if a temporary folder exists for this rule if (staticExportWorkRule.exists()) { createExportBackupFolders( staticExportRule, rule.getExportPath(), rule.getExportBackups().intValue(), OpenCms.getResourceManager().getFileTranslator().translateResource(rule.getName())); staticExportWorkRule.renameTo(new File(rule.getExportPath())); } } } else if (report.hasError()) { report.println(Messages.get().container(Messages.ERR_EXPORT_NOT_SUCCESSFUL_0), I_CmsReport.FORMAT_WARNING); } } /** * Returns the accept-charset header used for internal requests.<p> * * @return the accept-charset header */ public String getAcceptCharsetHeader() { return m_acceptCharsetHeader; } /** * Returns the accept-language header used for internal requests.<p> * * @return the accept-language header */ public String getAcceptLanguageHeader() { return m_acceptLanguageHeader; } /** * Returns a cached link for the given vfs name.<p> * * @param vfsName the name of the vfs resource to get the cached link for * * @return a cached link for the given vfs name, or null */ public String getCachedOnlineLink(String vfsName) { return m_cacheOnlineLinks.get(vfsName); } /** * Returns the key for the online, export and secure cache.<p> * * @param siteRoot the site root of the resource * @param uri the URI of the resource * * @return a key for the cache */ public String getCacheKey(String siteRoot, String uri) { return new StringBuffer(siteRoot).append(uri).toString(); } /** * Gets the default property value as a string representation.<p> * * @return <code>"true"</code> or <code>"false"</code> */ public String getDefault() { return String.valueOf(m_exportPropertyDefault); } /** * Returns the current default charset header.<p> * * @return the current default charset header */ public String getDefaultAcceptCharsetHeader() { return m_defaultAcceptCharsetHeader; } /** * Returns the current default locale header.<p> * * @return the current default locale header */ public String getDefaultAcceptLanguageHeader() { return m_defaultAcceptLanguageHeader; } /** * Returns the default prefix for exported links in the "real" file system.<p> * * @return the default prefix for exported links in the "real" file system */ public String getDefaultRfsPrefix() { return m_rfsPrefix; } /** * Returns the number of stored backups.<p> * * @return the number of stored backups */ public Integer getExportBackups() { if (m_staticExportBackups != null) { return m_staticExportBackups; } // if backups not configured set to default value return EXPORT_DEFAULT_BACKUPS; } /** * Returns the export data for the request, if null is returned no export is required.<p> * * @param request the request to check for export data * @param cms an initialized cms context (should be initialized with the "Guest" user only * * @return the export data for the request, if null is returned no export is required */ public CmsStaticExportData getExportData(HttpServletRequest request, CmsObject cms) { if (!isStaticExportEnabled()) { // export is disabled return null; } // build the rfs name for the export "on demand" String rfsName = request.getParameter(EXPORT_MARKER); if ((rfsName == null)) { rfsName = (String)request.getAttribute(EXPORT_ATTRIBUTE_ERROR_REQUEST_URI); } if (request.getHeader(CmsRequestUtil.HEADER_OPENCMS_EXPORT) != null) { // this is a request created by the static export and directly send to 404 handler // so remove the leading handler identification int prefix = rfsName.startsWith(getExportUrlPrefix()) ? getExportUrlPrefix().length() : 0; if (prefix > 0) { rfsName = rfsName.substring(prefix); } else { return null; } } if (!isValidRfsName(rfsName)) { // this is not an export request, no further processing is required return null; } // store the site root String storedSiteRoot = cms.getRequestContext().getSiteRoot(); try { // get the site root according to the HttpServletRequest CmsSite site = OpenCms.getSiteManager().matchRequest(request); // set the site root of the request context before getting the export data cms.getRequestContext().setSiteRoot(site.getSiteRoot()); // get the export data now CmsStaticExportData data = getRfsExportData(cms, rfsName); // check if we have an export link, // only return the data object if we really should export the resource if ((data != null) && isExportLink(cms, cms.getRequestContext().removeSiteRoot(data.getVfsName()))) { // if we have an export link return the export data object return data; } else { // otherwise if we have a link vfsName which should not be exported // return null for better error handling in the OpenCmsServlet return null; } } finally { // restore the site root cms.getRequestContext().setSiteRoot(storedSiteRoot); } } /** * Gets the export enabled value as a string representation.<p> * * @return <code>"true"</code> or <code>"false"</code> */ public String getExportEnabled() { return String.valueOf(m_staticExportEnabled); } /** * Returns the current folder matcher.<p> * * @return the current folder matcher */ public CmsExportFolderMatcher getExportFolderMatcher() { return m_exportFolderMatcher; } /** * Returns list of resources patterns which are part of the export.<p> * * @return the of resources patterns which are part of the export. */ public List<String> getExportFolderPatterns() { return Collections.unmodifiableList(m_exportFolders); } /** * Returns specific http headers for the static export.<p> * * If the header <code>Cache-Control</code> is set, OpenCms will not use its default headers.<p> * * @return the list of http export headers */ public List<String> getExportHeaders() { return Collections.unmodifiableList(m_exportHeaders); } /** * Returns a map of all export names with export name as key * and the vfs folder path as value.<p> * * @return a map of export names */ public Map<String, String> getExportnames() { Map<String, String> exportnames = new HashMap<String, String>(); if (m_exportnameResources == null) { m_exportnameResources = Collections.unmodifiableMap(computeVfsExportnames()); exportnames.putAll(m_exportnameResources); } else { exportnames.putAll(m_exportnameResources); } TreeMap<String, String> sortedMap = new TreeMap<String, String>(new CmsStringUtil.CmsSlashComparator()); sortedMap.putAll(exportnames); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_FINISHED_0)); } return Collections.unmodifiableMap(sortedMap); } /** * Returns the export path for the static export, that is the folder where the * static exported resources will be written to.<p> * * The returned value will be a directory like prefix. The value is configured * in the <code>opencms-importexport.xml</code> configuration file. An optimization * of the configured value will be performed, where all relative path information is resolved * (for example <code>/export/../static</code> will be resolved to <code>/export</code>. * Moreover, if the configured path ends with a <code>/</code>, this will be cut off * (for example <code>/export/</code> becomes <code>/export</code>.<p> * * This is resource name based, and based on the rfs-rules defined in the * <code>opencms-importexport.xml</code> configuration file.<p> * * @param vfsName the name of the resource to export * * @return the export path for the static export, that is the folder where the * * @see #getRfsPrefix(String) * @see #getVfsPrefix() */ public String getExportPath(String vfsName) { if (vfsName != null) { Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); if (rule.getSource().matcher(vfsName).matches()) { return rule.getExportPath(); } } } if (m_useTempDirs && isFullStaticExport()) { return getExportWorkPath(); } return m_staticExportPath; } /** * Returns the original configured export path for the static export without the complete rfs path, to be used * when re-writing the configuration.<p> * * This is required <b>only</b> to serialize the configuration again exactly as it was configured. * This method should <b>not</b> be used otherwise. Use <code>{@link #getExportPath(String)}</code> * to obtain the export path to use when exporting.<p> * * @return the original configured export path for the static export without the complete rfs path */ public String getExportPathForConfiguration() { return m_staticExportPathConfigured; } /** * Returns true if the default value for the resource property "export" is true.<p> * * @return true if the default value for the resource property "export" is true */ public boolean getExportPropertyDefault() { return m_exportPropertyDefault; } /** * Returns the export Rules.<p> * * @return the export Rules */ public List<CmsStaticExportExportRule> getExportRules() { return Collections.unmodifiableList(m_exportRules); } /** * Gets the list of resource suffixes which will be exported by default.<p> * * @return list of resource suffixes */ public List<String> getExportSuffixes() { return m_exportSuffixes; } /** * Returns the export URL used for internal requests for exporting resources that require a * request / response (like JSP).<p> * * @return the export URL used for internal requests for exporting resources like JSP */ public String getExportUrl() { return m_exportUrl; } /** * Returns the export URL used for internal requests with unsubstituted context values, to be used * when re-writing the configuration.<p> * * This is required <b>only</b> to serialize the configuration again exactly as it was configured. * This method should <b>not</b> be used otherwise. Use <code>{@link #getExportUrl()}</code> * to obtain the export path to use when exporting.<p> * * @return the export URL used for internal requests with unsubstituted context values */ public String getExportUrlForConfiguration() { return m_exportUrlConfigured; } /** * Returns the export URL used for internal requests for exporting resources that require a * request / response (like JSP) without http://servername.<p> * * @return the export URL used for internal requests for exporting resources like JSP without http://servername */ public String getExportUrlPrefix() { return m_exportUrlPrefix; } /** * Returns the export work path for the static export, that is the folder where the * static exported resources will be written to during the export process.<p> * * @return the export work path for the static export */ public String getExportWorkPath() { return m_staticExportWorkPath; } /** * Returns the original configured export work path for the static export without the complete rfs path, to be used * when re-writing the configuration.<p> * * @return the original configured export work path for the static export without the complete rfs path */ public String getExportWorkPathForConfiguration() { if (m_staticExportWorkPathConfigured != null) { return m_staticExportWorkPathConfigured; } // if work path not configured set to default value return EXPORT_DEFAULT_WORKPATH; } /** * Returns the configured static export handler class.<p> * * If not set, a new <code>{@link CmsAfterPublishStaticExportHandler}</code> is created and returned.<p> * * @return the configured static export handler class */ public I_CmsStaticExportHandler getHandler() { if (m_handler == null) { setHandler(CmsOnDemandStaticExportHandler.class.getName()); } return m_handler; } /** * Returns the configured link substitution handler class.<p> * * If not set, a new <code>{@link CmsDefaultLinkSubstitutionHandler}</code> is created and returned.<p> * * @return the configured link substitution handler class */ public I_CmsLinkSubstitutionHandler getLinkSubstitutionHandler() { if (m_linkSubstitutionHandler == null) { setLinkSubstitutionHandler(CmsDefaultLinkSubstitutionHandler.class.getName()); } return m_linkSubstitutionHandler; } /** * Gets the plain export optimization value as a string representation.<p> * * @return <code>"true"</code> or <code>"false"</code> */ public String getPlainExportOptimization() { return String.valueOf(m_quickPlainExport); } /** * Returns true if the quick plain export is enabled.<p> * * @return true if the quick plain export is enabled */ public boolean getQuickPlainExport() { return m_quickPlainExport; } /** * Gets the relative links value as a string representation.<p> * * @return <code>"true"</code> or <code>"false"</code> */ public String getRelativeLinks() { return String.valueOf(m_exportRelativeLinks); } /** * Returns the remote address used for internal requests.<p> * * @return the remote address */ public String getRemoteAddr() { return m_remoteAddr; } /** * Returns the remote address.<p> * * @return the remote address */ public String getRemoteAddress() { return m_remoteAddr; } /** * Returns the static export rfs name for a given vfs resource.<p> * * @param cms an initialized cms context * @param vfsName the name of the vfs resource * * @return the static export rfs name for a give vfs resource * * @see #getVfsName(CmsObject, String) * @see #getRfsName(CmsObject, String, String) */ public String getRfsName(CmsObject cms, String vfsName) { return getRfsName(cms, vfsName, null); } /** * Returns the static export rfs name for a given vfs resource where the link to the * resource includes request parameters.<p> * * @param cms an initialized cms context * @param vfsName the name of the vfs resource * @param parameters the parameters of the link pointing to the resource * * @return the static export rfs name for a give vfs resource */ public String getRfsName(CmsObject cms, String vfsName, String parameters) { String rfsName; try { CmsResource vfsRes = null; try { vfsRes = cms.readResource(vfsName); I_CmsDetailPageFinder finder = OpenCms.getADEManager().getDetailPageFinder(); String detailPage = finder.getDetailPage(cms, vfsRes.getRootPath(), cms.getRequestContext().getUri()); if (detailPage != null) { vfsName = CmsStringUtil.joinPaths( detailPage, CmsDetailPageUtil.getBestUrlName(cms, vfsRes.getStructureId()), "/"); } } catch (CmsVfsResourceNotFoundException e) { // ignore } rfsName = getRfsNameWithExportName(cms, vfsName); String extension = CmsFileUtil.getExtension(rfsName); // check if the VFS resource is a JSP page with a ".jsp" ending // in this case the name suffix must be build with special care, // usually it must be set to ".html" boolean isJsp = extension.equals(".jsp"); if (isJsp) { String suffix = null; try { CmsResource res = cms.readResource(vfsName); isJsp = (CmsResourceTypeJsp.isJsp(res)); // if the resource is a plain resource then no change in suffix is required if (isJsp) { suffix = cms.readPropertyObject(vfsName, CmsPropertyDefinition.PROPERTY_EXPORTSUFFIX, true).getValue( ".html"); } } catch (CmsVfsResourceNotFoundException e) { // resource has been deleted, so we are not able to get the right extension from the properties // try to figure out the right extension from file system File rfsFile = new File( CmsFileUtil.normalizePath(getExportPath(cms.getRequestContext().addSiteRoot(vfsName)) + rfsName)); File parent = rfsFile.getParentFile(); if (parent != null) { File[] paramVariants = parent.listFiles(new CmsPrefixFileFilter(rfsFile.getName())); if ((paramVariants != null) && (paramVariants.length > 0)) { // take the first suffix = paramVariants[0].getAbsolutePath().substring(rfsFile.getAbsolutePath().length()); } } else { // if no luck, try the default extension suffix = ".html"; } } if ((suffix != null) && !extension.equals(suffix.toLowerCase())) { rfsName += suffix; extension = suffix; } } if (parameters != null) { // build the RFS name for the link with parameters rfsName = CmsFileUtil.getRfsPath(rfsName, extension, parameters); // we have found a rfs name for a vfs resource with parameters, save it to the database try { cms.writeStaticExportPublishedResource( rfsName, CmsStaticExportManager.EXPORT_LINK_WITH_PARAMETER, parameters, System.currentTimeMillis()); } catch (CmsException e) { LOG.error(Messages.get().getBundle().key(Messages.LOG_WRITE_FAILED_1, rfsName), e); } } } catch (CmsException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getLocalizedMessage(), e); } // ignore exception, return vfsName as rfsName rfsName = vfsName; } // add export rfs prefix and return result if (!vfsName.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) { return getRfsPrefix(cms.getRequestContext().addSiteRoot(vfsName)).concat(rfsName); } else { // check if we are generating a link to a related resource in the same rfs rule String source = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri()); Iterator<CmsStaticExportRfsRule> it = getRfsRules().iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); if (rule.getSource().matcher(source).matches() && rule.match(vfsName)) { return rule.getRfsPrefix().concat(rfsName); } } // this is a link across rfs rules return getRfsPrefix(cms.getRequestContext().getSiteRoot() + "/").concat(rfsName); } } /** * Returns the prefix for exported links in the "real" file system.<p> * * The returned value will be a directory like prefix. The value is configured * in the <code>opencms-importexport.xml</code> configuration file. An optimization * of the configured value will be performed, where all relative path information is resolved * (for example <code>/export/../static</code> will be resolved to <code>/export</code>. * Moreover, if the configured path ends with a <code>/</code>, this will be cut off * (for example <code>/export/</code> becomes <code>/export</code>.<p> * * This is resource name based, and based on the rfs-rules defined in the * <code>opencms-importexport.xml</code> configuration file.<p> * * @param vfsName the name of the resource to export * * @return the prefix for exported links in the "real" file system * * @see #getExportPath(String) * @see #getVfsPrefix() */ public String getRfsPrefix(String vfsName) { if (vfsName != null) { Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); if (rule.getSource().matcher(vfsName).matches()) { return rule.getRfsPrefix(); } } } return m_rfsPrefix; } /** * Returns the original configured prefix for exported links in the "real" file, to be used * when re-writing the configuration.<p> * * This is required <b>only</b> to serialize the configuration again exactly as it was configured. * This method should <b>not</b> be used otherwise. Use <code>{@link #getRfsPrefix(String)}</code> * to obtain the rfs prefix to use for the exported links.<p> * * @return the original configured prefix for exported links in the "real" file */ public String getRfsPrefixForConfiguration() { return m_rfsPrefixConfigured; } /** * Returns the rfs Rules.<p> * * @return the rfs Rules */ public List<CmsStaticExportRfsRule> getRfsRules() { return Collections.unmodifiableList(m_rfsRules); } /** * Returns the vfs name of the test resource.<p> * * @return the vfs name of the test resource. */ public String getTestResource() { return m_testResource; } /** * Returns the export data for a requested resource, if null is returned no export is required.<p> * * @param cms an initialized cms context (should be initialized with the "Guest" user only * @param vfsName the VFS name of the resource requested * * @return the export data for the request, if null is returned no export is required */ public CmsStaticExportData getVfsExportData(CmsObject cms, String vfsName) { return getRfsExportData(cms, getRfsName(cms, vfsName)); } /** * Returns the VFS name for the given RFS name, being the exact reverse of <code>{@link #getRfsName(CmsObject, String)}</code>.<p> * * Returns <code>null</code> if no matching VFS resource can be found for the given RFS name.<p> * * @param cms the current users OpenCms context * @param rfsName the RFS name to get the VFS name for * * @return the VFS name for the given RFS name, or <code>null</code> if the RFS name does not match to the VFS * * @see #getRfsName(CmsObject, String) */ public String getVfsName(CmsObject cms, String rfsName) { CmsStaticExportData data = getRfsExportData(cms, rfsName); if (data != null) { String result = data.getVfsName(); if ((result != null) && result.startsWith(cms.getRequestContext().getSiteRoot())) { result = result.substring(cms.getRequestContext().getSiteRoot().length()); } return result; } return null; } /** * Returns the VFS name from a given RFS name.<p> * * The RFS name must not contain the RFS prefix.<p> * * @param cms an initialized OpenCms user context * @param rfsName the name of the RFS resource * * @return the name of the VFS resource * * @throws CmsVfsResourceNotFoundException if something goes wrong */ public CmsStaticExportData getVfsNameInternal(CmsObject cms, String rfsName) throws CmsVfsResourceNotFoundException { String storedSiteRoot = cms.getRequestContext().getSiteRoot(); try { cms.getRequestContext().setSiteRoot("/"); // try to find a match with the "exportname" folders String path = rfsName; // in case of folders, remove the trailing slash // in case of files, remove the filename and trailing slash path = path.substring(0, path.lastIndexOf('/')); // cache the export names Map<String, String> exportnameResources = getExportnames(); while (true) { // exportnameResources are only folders! String expName = exportnameResources.get(path + '/'); if (expName == null) { if (path.length() == 0) { break; } path = path.substring(0, path.lastIndexOf('/')); continue; } // this will be a root path! String vfsName = expName + rfsName.substring(path.length() + 1); try { return readResource(cms, vfsName); } catch (CmsVfsResourceNotFoundException e) { // if already checked all parts of the path we can stop here. // This is the case if the "/" is set as "exportname" on any vfs resource if (path.length() == 0) { break; } // continue with trying out the other exportname to find a match (may be a multiple prefix) path = path.substring(0, path.lastIndexOf('/')); continue; } catch (CmsException e) { // should never happen LOG.error(e.getLocalizedMessage(), e); break; } } // try to read name of export resource by reading the resource directly try { return readResource(cms, rfsName); } catch (Throwable t) { // resource not found if (LOG.isDebugEnabled()) { LOG.debug( Messages.get().getBundle().key(Messages.ERR_EXPORT_FILE_FAILED_1, new String[] {rfsName}), t); } } // finally check if its a modified jsp resource int extPos = rfsName.lastIndexOf('.'); // first cut of the last extension if (extPos >= 0) { String cutName = rfsName.substring(0, extPos); int pos = cutName.lastIndexOf('.'); if (pos >= 0) { // now check if remaining String ends with ".jsp" String extension = cutName.substring(pos).toLowerCase(); if (".jsp".equals(extension)) { return getVfsNameInternal(cms, cutName); } } } } finally { cms.getRequestContext().setSiteRoot(storedSiteRoot); } throw new CmsVfsResourceNotFoundException(org.opencms.db.generic.Messages.get().container( org.opencms.db.generic.Messages.ERR_READ_RESOURCE_1, rfsName)); } /** * Returns the prefix for the internal in the VFS.<p> * * The returned value will be a directory like prefix. The value is configured * in the <code>opencms-importexport.xml</code> configuration file. An optimization * of the configured value will be performed, where all relative path information is resolved * (for example <code>/opencms/../mycms</code> will be resolved to <code>/mycms</code>. * Moreover, if the configured path ends with a <code>/</code>, this will be cut off * (for example <code>/opencms/</code> becomes <code>/opencms</code>.<p> * * @return the prefix for the internal in the VFS * * @see #getExportPath(String) * @see #getRfsPrefix(String) */ public String getVfsPrefix() { return m_vfsPrefix; } /** * Returns the original configured prefix for internal links in the VFS, to be used * when re-writing the configuration.<p> * * This is required <b>only</b> to serialize the configuration again exactly as it was configured. * This method should <b>not</b> be used otherwise. Use <code>{@link #getVfsPrefix()}</code> * to obtain the VFS prefix to use for the internal links.<p> * * @return the original configured prefix for internal links in the VFS */ public String getVfsPrefixForConfiguration() { return m_vfsPrefixConfigured; } /** * Initializes the static export manager with the OpenCms system configuration.<p> * * @param cms an OpenCms context object */ public void initialize(CmsObject cms) { // initialize static export RFS path (relative to web application) m_staticExportPath = normalizeExportPath(m_staticExportPathConfigured); m_staticExportWorkPath = normalizeExportPath(getExportWorkPathForConfiguration()); if (m_staticExportPath.equals(OpenCms.getSystemInfo().getWebApplicationRfsPath())) { throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INVALID_EXPORT_PATH_0)); } // initialize prefix variables m_rfsPrefix = normalizeRfsPrefix(m_rfsPrefixConfigured); Iterator<CmsStaticExportRfsRule> itRfsRules = m_rfsRules.iterator(); while (itRfsRules.hasNext()) { CmsStaticExportRfsRule rule = itRfsRules.next(); try { rule.setExportPath(normalizeExportPath(rule.getExportPathConfigured())); } catch (CmsIllegalArgumentException e) { CmsLog.INIT.warn(e.getMessageContainer()); rule.setExportPath(m_staticExportPath); } try { rule.setExportWorkPath(normalizeExportPath(rule.getExportWorkPathConfigured())); } catch (CmsIllegalArgumentException e) { CmsLog.INIT.warn(e.getMessageContainer()); rule.setExportWorkPath(m_staticExportWorkPath); } rule.setRfsPrefix(normalizeRfsPrefix(rule.getRfsPrefixConfigured())); } m_vfsPrefix = insertContextStrings(m_vfsPrefixConfigured); m_vfsPrefix = CmsFileUtil.normalizePath(m_vfsPrefix, '/'); if (CmsResource.isFolder(m_vfsPrefix)) { // ensure prefix does NOT end with a folder '/' m_vfsPrefix = m_vfsPrefix.substring(0, m_vfsPrefix.length() - 1); } if (CmsLog.INIT.isDebugEnabled()) { if (cms != null) { CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_SE_MANAGER_CREATED_1, cms)); } else { CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_SE_MANAGER_CREATED_0)); } } Map<String, String> lruMap1 = CmsCollectionsGenericWrapper.createLRUMap(2048); m_cacheOnlineLinks = Collections.synchronizedMap(lruMap1); // map must be of type "LRUMap" so that memory monitor can acecss all information OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheOnlineLinks", lruMap1); Map<String, CmsStaticExportData> lruMap2 = CmsCollectionsGenericWrapper.createLRUMap(2048); m_cacheExportUris = Collections.synchronizedMap(lruMap2); // map must be of type "LRUMap" so that memory monitor can acecss all information OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportUris", lruMap2); Map<String, Boolean> lruMap3 = CmsCollectionsGenericWrapper.createLRUMap(2048); m_cacheSecureLinks = Collections.synchronizedMap(lruMap3); // map must be of type "LRUMap" so that memory monitor can acecss all information OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheSecureLinks", lruMap3); Map<String, Boolean> lruMap4 = CmsCollectionsGenericWrapper.createLRUMap(2048); m_cacheExportLinks = Collections.synchronizedMap(lruMap4); // map must be of type "LRUMap" so that memory monitor can acecss all information OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportLinks", lruMap4); // register this object as event listener OpenCms.addCmsEventListener(this, new int[] { I_CmsEventListener.EVENT_PUBLISH_PROJECT, I_CmsEventListener.EVENT_CLEAR_CACHES, I_CmsEventListener.EVENT_UPDATE_EXPORTS}); m_exportFolderMatcher = new CmsExportFolderMatcher(m_exportFolders, m_testResource); // get the default accept-language header value m_defaultAcceptLanguageHeader = CmsAcceptLanguageHeaderParser.createLanguageHeader(); // get the default accept-charset header value m_defaultAcceptCharsetHeader = OpenCms.getSystemInfo().getDefaultEncoding(); // get the export url prefix int pos = m_exportUrl.indexOf("://"); if (pos > 0) { // absolute link, remove http://servername int pos2 = m_exportUrl.indexOf('/', pos + 3); if (pos2 > 0) { m_exportUrlPrefix = m_exportUrl.substring(pos2); } else { // should never happen m_exportUrlPrefix = ""; } } else { m_exportUrlPrefix = m_exportUrl; } if (CmsLog.INIT.isInfoEnabled()) { if (isStaticExportEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STATIC_EXPORT_ENABLED_0)); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_DEFAULT_1, Boolean.valueOf(getExportPropertyDefault()))); itRfsRules = m_rfsRules.iterator(); while (itRfsRules.hasNext()) { CmsStaticExportRfsRule rfsRule = itRfsRules.next(); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_EXPORT_PATH_2, rfsRule.getSource(), rfsRule.getExportPath())); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_RFS_PREFIX_2, rfsRule.getSource(), rfsRule.getRfsPrefix())); if (rfsRule.getUseRelativeLinks() != null) { if (rfsRule.getUseRelativeLinks().booleanValue()) { CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_RELATIVE_LINKS_1, rfsRule.getSource())); } else { CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_ABSOLUTE_LINKS_1, rfsRule.getSource())); } } } // default rule CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_EXPORT_PATH_2, "/", m_staticExportPath)); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_RFS_RULE_RFS_PREFIX_2, "/", m_rfsPrefix)); if (m_exportRelativeLinks) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_RFS_RULE_RELATIVE_LINKS_1, "/")); } else { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_RFS_RULE_ABSOLUTE_LINKS_1, "/")); } CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_VFS_PREFIX_1, getVfsPrefix())); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_EXPORT_HANDLER_1, getHandler().getClass().getName())); CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_URL_1, getExportUrl())); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_EXPORT_OPTIMIZATION_1, getPlainExportOptimization())); CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_TESTRESOURCE_1, getTestResource())); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_LINKSUBSTITUTION_HANDLER_1, getLinkSubstitutionHandler().getClass().getName())); } else { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_STATIC_EXPORT_DISABLED_0)); } } } /** * Checks if the static export is required for the given VFS resource.<p> * * Please note that the given OpenCms user context is NOT used to read the resource. * The check for export is always done with the permissions of the "Export" user. * The provided user context is just used to get the current site root.<p> * * Since the "Export" user always operates in the "Online" project, the resource * is also read from the "Online" project, not from the current project of the given * OpenCms context.<p> * * @param cms the current users OpenCms context * @param vfsName the VFS resource name to check * * @return <code>true</code> if static export is required for the given VFS resource */ public boolean isExportLink(CmsObject cms, String vfsName) { LOG.info("isExportLink? " + vfsName); String siteRoot = cms.getRequestContext().getSiteRoot(); // vfsname may still be a root path for a site with a different site root CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(vfsName); if (site != null) { siteRoot = site.getSiteRoot(); vfsName = CmsStringUtil.joinPaths("/", vfsName.substring(siteRoot.length())); } String cacheKey = getCacheKey(siteRoot, vfsName); Boolean exportResource = getCacheExportLinks().get(cacheKey); if (exportResource != null) { return exportResource.booleanValue(); } boolean result = false; try { // static export must always be checked with the export users permissions, // not the current users permissions CmsObject exportCms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); exportCms.getRequestContext().setSiteRoot(siteRoot); // let's look up export property in VFS CmsResource exportRes = CmsDetailPageUtil.lookupPage(exportCms, vfsName); String exportValue = exportCms.readPropertyObject( exportCms.getSitePath(exportRes), CmsPropertyDefinition.PROPERTY_EXPORT, true).getValue(); if (exportValue == null) { // no setting found for "export" property if (getExportPropertyDefault()) { // if the default is "true" we always export result = true; } else { // check if the resource is exportable by suffix result = isSuffixExportable(vfsName); } } else { // "export" value found, if it was "true" we export result = Boolean.valueOf(exportValue).booleanValue(); } } catch (CmsException e) { // no export required (probably security issues, e.g. no access for export user) LOG.debug(e.getLocalizedMessage(), e); } getCacheExportLinks().put(cacheKey, Boolean.valueOf(result)); return result; } /** * Returns true if the export process is a full static export.<p> * * @return true if the export process is a full static export */ public boolean isFullStaticExport() { return m_fullStaticExport; } /** * Returns <code>true</code> if the given VFS resource should be transported through a secure channel.<p> * * The secure mode is only checked in the "Online" project. * If the given OpenCms context is currently not in the "Online" project, * <code>false</code> is returned.<p> * * The given resource is read from the site root of the provided OpenCms context.<p> * * @param cms the current users OpenCms context * @param vfsName the VFS resource name to check * * @return <code>true</code> if the given VFS resource should be transported through a secure channel * * @see CmsStaticExportManager#isSecureLink(CmsObject, String, String) */ public boolean isSecureLink(CmsObject cms, String vfsName) { if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) { return false; } String cacheKey = OpenCms.getStaticExportManager().getCacheKey(cms.getRequestContext().getSiteRoot(), vfsName); Boolean secureResource = OpenCms.getStaticExportManager().getCacheSecureLinks().get(cacheKey); if (secureResource == null) { try { String secureProp = cms.readPropertyObject(vfsName, CmsPropertyDefinition.PROPERTY_SECURE, true).getValue(); secureResource = Boolean.valueOf(secureProp); // only cache result if read was successfull OpenCms.getStaticExportManager().getCacheSecureLinks().put(cacheKey, secureResource); } catch (CmsVfsResourceNotFoundException e) { secureResource = Boolean.FALSE; // resource does not exist, no secure link will be required for any user OpenCms.getStaticExportManager().getCacheSecureLinks().put(cacheKey, secureResource); } catch (Exception e) { // no secure link required (probably security issues, e.g. no access for current user) // however other users may be allowed to read the resource, so the result can't be cached secureResource = Boolean.FALSE; } } return secureResource.booleanValue(); } /** * Returns <code>true</code> if the given VFS resource that is located under the * given site root should be transported through a secure channel.<p> * * @param cms the current users OpenCms context * @param vfsName the VFS resource name to check * @param siteRoot the site root where the the VFS resource should be read * * @return <code>true</code> if the given VFS resource should be transported through a secure channel * * @see #isSecureLink(CmsObject, String) */ public boolean isSecureLink(CmsObject cms, String vfsName, String siteRoot) { if (siteRoot == null) { return isSecureLink(cms, vfsName); } // the site root of the cms object has to be changed so that the property can be read String storedSiteRoot = cms.getRequestContext().getSiteRoot(); try { cms.getRequestContext().setSiteRoot(siteRoot); return isSecureLink(cms, vfsName); } finally { cms.getRequestContext().setSiteRoot(storedSiteRoot); } } /** * Returns true if the static export is enabled.<p> * * @return true if the static export is enabled */ public boolean isStaticExportEnabled() { return m_staticExportEnabled; } /** * Returns true if the given resource name is exportable because of it's suffix.<p> * * @param resourceName the name to check * @return true if the given resource name is exportable because of it's suffix */ public boolean isSuffixExportable(String resourceName) { if (resourceName == null) { return false; } int pos = resourceName.lastIndexOf('.'); if (pos >= 0) { String suffix = resourceName.substring(pos).toLowerCase(); return m_exportSuffixes.contains(suffix); } return false; } /** * Checks if we have to use temporary directories during export.<p> * * @return <code>true</code> if using temporary directories */ public boolean isUseTempDir() { return m_useTempDirs; } /** * Returns true if the links in the static export should be relative.<p> * * @param vfsName the name of the resource to export * * @return true if the links in the static export should be relative */ public boolean relativeLinksInExport(String vfsName) { if (vfsName != null) { Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); if (rule.getSource().matcher(vfsName).matches()) { return rule.getUseRelativeLinks() != null ? rule.getUseRelativeLinks().booleanValue() : m_exportRelativeLinks; } } } return m_exportRelativeLinks; } /** * Sets the accept-charset header value.<p> * * @param value accept-language header value */ public void setAcceptCharsetHeader(String value) { m_acceptCharsetHeader = value; } /** * Sets the accept-language header value.<p> * * @param value accept-language header value */ public void setAcceptLanguageHeader(String value) { m_acceptLanguageHeader = value; } /** * Sets the default property value.<p> * * @param value must be <code>true</code> or <code>false</code> */ public void setDefault(String value) { m_exportPropertyDefault = Boolean.valueOf(value).booleanValue(); } /** * Sets the number of backups for the static export.<p> * * @param backup number of backups */ public void setExportBackups(String backup) { m_staticExportBackups = new Integer(backup); } /** * Sets the export enabled value.<p> * * @param value must be <code>true</code> or <code>false</code> */ public void setExportEnabled(String value) { m_staticExportEnabled = Boolean.valueOf(value).booleanValue(); } /** * Adds a resource pattern to the list of resources which are part of the export.<p> * * @param folder the folder pattern to add to the list. */ public void setExportFolderPattern(String folder) { m_exportFolders.add(folder); } /** * Sets specific http header for the static export.<p> * * The format of the headers must be "header:value".<p> * * @param exportHeader a specific http header */ public void setExportHeader(String exportHeader) { if (CmsStringUtil.splitAsArray(exportHeader, ':').length == 2) { if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_EXPORT_HEADERS_1, exportHeader)); } m_exportHeaders.add(exportHeader); } else { if (CmsLog.INIT.isWarnEnabled()) { CmsLog.INIT.warn(Messages.get().getBundle().key(Messages.INIT_INVALID_HEADER_1, exportHeader)); } } } /** * Sets the path where the static export is written.<p> * * @param path the path where the static export is written */ public void setExportPath(String path) { m_staticExportPathConfigured = path; } /** * Adds a suffix to the list of resource suffixes which will be exported by default.<p> * * @param suffix the suffix to add to the list. */ public void setExportSuffix(String suffix) { m_exportSuffixes.add(suffix.toLowerCase()); } /** * Sets the export url.<p> * * @param url the export url */ public void setExportUrl(String url) { m_exportUrl = insertContextStrings(url); m_exportUrlConfigured = url; } /** * Sets the path where the static export is temporarily written.<p> * * @param path the path where the static export is temporarily written */ public void setExportWorkPath(String path) { m_staticExportWorkPathConfigured = path; } /** * Sets the link substitution handler class.<p> * * @param handlerClassName the link substitution handler class name */ public void setHandler(String handlerClassName) { try { m_handler = (I_CmsStaticExportHandler)Class.forName(handlerClassName).newInstance(); } catch (Exception e) { // should never happen LOG.error(e.getLocalizedMessage(), e); } } /** * Sets the static export handler class.<p> * * @param handlerClassName the static export handler class name */ public void setLinkSubstitutionHandler(String handlerClassName) { try { m_linkSubstitutionHandler = (I_CmsLinkSubstitutionHandler)Class.forName(handlerClassName).newInstance(); } catch (Exception e) { // should never happen LOG.error(e.getLocalizedMessage(), e); } } /** * Sets the plain export optimization value.<p> * * @param value must be <code>true</code> or <code>false</code> */ public void setPlainExportOptimization(String value) { m_quickPlainExport = Boolean.valueOf(value).booleanValue(); } /** * Sets the relative links value.<p> * * @param value must be <code>true</code> or <code>false</code> */ public void setRelativeLinks(String value) { m_exportRelativeLinks = Boolean.valueOf(value).booleanValue(); } /** * Sets the remote address which will be used for internal requests during the static export.<p> * * @param addr the remote address to be used */ public void setRemoteAddr(String addr) { m_remoteAddr = addr; } /** * Sets the prefix for exported links in the "real" file system.<p> * * @param rfsPrefix the prefix for exported links in the "real" file system */ public void setRfsPrefix(String rfsPrefix) { m_rfsPrefixConfigured = rfsPrefix; } /** * Sets the test resource.<p> * * @param testResource the vfs name of the test resource */ public void setTestResource(String testResource) { m_testResource = testResource; } /** * Sets the prefix for internal links in the vfs.<p> * * @param vfsPrefix the prefix for internal links in the vfs */ public void setVfsPrefix(String vfsPrefix) { m_vfsPrefixConfigured = vfsPrefix; } /** * Shuts down all this static export manager.<p> * * This is required since there may still be a thread running when the system is being shut down.<p> */ public synchronized void shutDown() { int count = 0; // if the handler is still running, we must wait up to 30 seconds until it is finished while ((count < HANDLER_FINISH_TIME) && m_handler.isBusy()) { count++; try { if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_STATIC_EXPORT_SHUTDOWN_3, m_handler.getClass().getName(), String.valueOf(count), String.valueOf(HANDLER_FINISH_TIME))); } wait(1000); } catch (InterruptedException e) { // if interrupted we ignore the handler, this will produce some log messages but should be ok count = HANDLER_FINISH_TIME; } } if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName())); } } /** * Clears the caches in the export manager.<p> * * @param event the event that requested to clear the caches */ protected void clearCaches(CmsEvent event) { // synchronization of this method is not required as the individual maps are all synchronized maps anyway, // and setExportnames() is doing it's own synchronization // flush all caches m_cacheOnlineLinks.clear(); m_cacheExportUris.clear(); m_cacheSecureLinks.clear(); m_cacheExportLinks.clear(); m_exportnameResources = null; if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLUSHED_CACHES_1, new Integer(event.getType()))); } } /** * Creates the backup folders for the given export folder and deletes the oldest if the maximum number is reached.<p> * * @param staticExport folder for which a new backup folder has to be created * @param exportPath export path to create backup path out of it * @param exportBackups number of maximum * @param ruleBackupExtension extension for rule based backups */ protected void createExportBackupFolders( File staticExport, String exportPath, int exportBackups, String ruleBackupExtension) { if (staticExport.exists()) { String backupFolderName = exportPath.substring(0, exportPath.lastIndexOf(File.separator) + 1); if (ruleBackupExtension != null) { backupFolderName = backupFolderName + EXPORT_BACKUP_FOLDER_NAME + ruleBackupExtension; } else { backupFolderName = backupFolderName + EXPORT_BACKUP_FOLDER_NAME; } for (int i = exportBackups; i > 0; i--) { File staticExportBackupOld = new File(backupFolderName + new Integer(i).toString()); if (staticExportBackupOld.exists()) { if ((i + 1) > exportBackups) { // delete folder if it is the last backup folder CmsFileUtil.purgeDirectory(staticExportBackupOld); } else { // set backup folder to the next backup folder name staticExportBackupOld.renameTo(new File(backupFolderName + new Integer(i + 1).toString())); } } // old export folder rename to first backup folder if (i == 1) { staticExport.renameTo(staticExportBackupOld); } } // if no backups will be stored the old export folder has to be deleted if (exportBackups == 0) { CmsFileUtil.purgeDirectory(staticExport); } } } /** * Creates the parent folder for a exported resource in the RFS.<p> * * @param exportPath the path to export the file * @param rfsName the rfs name of the resource * * @throws CmsException if the folder could not be created */ protected void createExportFolder(String exportPath, String rfsName) throws CmsException { String exportFolderName = CmsFileUtil.normalizePath(exportPath + CmsResource.getFolderPath(rfsName)); File exportFolder = new File(exportFolderName); if (!exportFolder.exists()) { // in case of concurrent requests to create this folder, check the folder existence again if (!exportFolder.mkdirs() && !exportFolder.exists()) { throw new CmsStaticExportException(Messages.get().container(Messages.ERR_CREATE_FOLDER_1, rfsName)); } } } /** * Returns the cacheExportLinks.<p> * * @return the cacheExportLinks */ protected Map<String, Boolean> getCacheExportLinks() { return m_cacheExportLinks; } /** * Returns the cacheSecureLinks.<p> * * @return the cacheSecureLinks */ protected Map<String, Boolean> getCacheSecureLinks() { return m_cacheSecureLinks; } /** * Returns the export data for a requested resource, if null is returned no export is required.<p> * * @param cms an initialized cms context (should be initialized with the "Export" user only) * @param uri the uri, ie RFS name of the requested resource, with or without the 'export' prefix * * @return the export data for the request, if null is returned no export is required */ protected CmsStaticExportData getRfsExportData(CmsObject cms, String uri) { // cut export prefix from name String rfsName = uri.substring(getRfsPrefixForRfsName(uri).length()); // check if we have the result already in the cache CmsStaticExportData data = m_cacheExportUris.get(rfsName); if (data == null) { // export uri not in cache, must look up the file in the VFS try { data = getVfsNameInternal(cms, rfsName); } catch (CmsVfsResourceNotFoundException e) { // could happen but is the expected behavior because // the accoring vfs resource for the given rfsname could not be found // maybe the rfsname has parameters set -> go on } } if (data == null) { // it could be a translated resourcename with parameters, // so make a lookup in the published resources table try { String parameters = cms.readStaticExportPublishedResourceParameters(rfsName); // there was a match in the db table, so get the StaticExportData if (CmsStringUtil.isNotEmpty(parameters)) { // get the rfs base string without the parameter hashcode String rfsBaseName = rfsName.substring(0, rfsName.lastIndexOf('_')); if (rfsBaseName.endsWith(EXPORT_DEFAULT_FILE)) { rfsBaseName = rfsBaseName.substring(0, rfsBaseName.length() - EXPORT_DEFAULT_FILE.length()); } // get the vfs base name, which is later used to read the resource in the vfs data = getVfsNameInternal(cms, rfsBaseName); if (data != null) { data.setParameters(parameters); } } } catch (CmsVfsResourceNotFoundException e) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_NO_INTERNAL_VFS_RESOURCE_FOUND_1, new String[] {rfsName})); } } catch (CmsException e) { // ignore, resource does not exist if (LOG.isWarnEnabled()) { LOG.warn( Messages.get().getBundle().key(Messages.ERR_EXPORT_FILE_FAILED_1, new String[] {rfsName}), e); } } } if (data == null) { // no export data found data = new CmsStaticExportData(CACHEVALUE_404, rfsName, null, null); } m_cacheExportUris.put(rfsName, data); // this object comparison is safe, see caller method if (data.getVfsName() != CACHEVALUE_404) { if (data.getResource().isFolder() && !CmsResource.isFolder(rfsName)) { // be sure that folders are folders! rfsName += "/"; } data.setRfsName(rfsName); // this uri can be exported return data; } // this uri can not be exported return null; } /** * Returns the rfs name for a given vfs name with consideration of the export name.<p> * * @param cms the cms obejct * @param vfsName the the name of the vfs resource * * @return the rfs name for a given vfs name with consideration of the export name */ protected String getRfsNameWithExportName(CmsObject cms, String vfsName) { String rfsName = vfsName; try { // check if the resource folder (or a parent folder) has the "exportname" property set String name = CmsResource.getName(vfsName).replaceAll("/$", ""); CmsUUID detailId = cms.readIdForUrlName(name); String propertyReadPath; if (detailId == null) { propertyReadPath = CmsResource.getFolderPath(rfsName); } else { propertyReadPath = CmsResource.getFolderPath(rfsName.replaceAll("/$", "")); } CmsProperty exportNameProperty = cms.readPropertyObject( propertyReadPath, CmsPropertyDefinition.PROPERTY_EXPORTNAME, true); if (exportNameProperty.isNullProperty()) { // if "exportname" is not set we must add the site root rfsName = cms.getRequestContext().addSiteRoot(rfsName); } else { // "exportname" property is set String exportname = exportNameProperty.getValue(); if (exportname.charAt(0) != '/') { exportname = '/' + exportname; } if (exportname.charAt(exportname.length() - 1) != '/') { exportname = exportname + '/'; } String value = null; boolean cont; String resourceName = rfsName; // resourceName can be the detail page URI do { // find out where the export name was set, to replace these parent folders in the RFS name try { CmsProperty prop = cms.readPropertyObject( resourceName, CmsPropertyDefinition.PROPERTY_EXPORTNAME, false); if (prop.isIdentical(exportNameProperty)) { // look for the right position in path value = prop.getValue(); } cont = (value == null) && (resourceName.length() > 1); } catch (CmsVfsResourceNotFoundException e) { // this is for publishing deleted resources cont = (resourceName.length() > 1); } catch (CmsSecurityException se) { // a security exception (probably no read permission) we return the current result cont = false; } if (cont) { resourceName = CmsResource.getParentFolder(resourceName); } } while (cont); rfsName = exportname + rfsName.substring(resourceName.length()); } } catch (CmsException e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getLocalizedMessage(), e); } // ignore exception, return vfsName as rfsName rfsName = vfsName; } return rfsName; } /** * Returns the longest rfs prefix matching a given already translated rfs name.<p> * * @param rfsName the rfs name * * @return its rfs prefix * * @see #getRfsPrefix(String) */ protected String getRfsPrefixForRfsName(String rfsName) { String retVal = ""; // default case if (rfsName.startsWith(m_rfsPrefix + "/")) { retVal = m_rfsPrefix; } // additional rules Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); String rfsPrefix = rule.getRfsPrefix(); if (rfsName.startsWith(rfsPrefix + "/") && (retVal.length() < rfsPrefix.length())) { retVal = rfsPrefix; } } return retVal; } /** * Substitutes the ${CONTEXT_NAME} and ${SERVLET_NAME} in a path with the real values.<p> * * @param path the path to substitute * @return path with real context values */ protected String insertContextStrings(String path) { // create a new macro resolver CmsMacroResolver resolver = CmsMacroResolver.newInstance(); // add special mappings for macros resolver.addMacro("CONTEXT_NAME", OpenCms.getSystemInfo().getContextPath()); resolver.addMacro("SERVLET_NAME", OpenCms.getSystemInfo().getServletPath()); // resolve the macros return resolver.resolveMacros(path); } /** * Returns true if the rfs Name match against any of the defined export urls.<p> * * @param rfsName the rfs Name to validate * * @return true if the rfs Name match against any of the defined export urls */ protected boolean isValidRfsName(String rfsName) { if (rfsName != null) { // default case if (rfsName.startsWith(m_rfsPrefix + "/")) { return true; } // additional rules Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); String rfsPrefix = rule.getRfsPrefix() + "/"; if (rfsName.startsWith(rfsPrefix)) { return true; } } } return false; } /** * Checks if a String is a valid URL.<p> * * @param inputString The String to check can be <code>null</code> * * @return <code>true</code> if the String is not <code>null</code> and a valid URL */ protected boolean isValidURL(String inputString) { boolean isValid = false; try { if (inputString != null) { URL tempURL = new URL(inputString); isValid = (tempURL.getProtocol() != null); } } catch (MalformedURLException mue) { // ignore because it is not harmful } return isValid; } /** * Returns a normalized export path.<p> * * Replacing macros, normalizing the path and taking care of relative paths.<p> * * @param exportPath the export path to normalize * * @return the normalized export path */ protected String normalizeExportPath(String exportPath) { String result = insertContextStrings(exportPath); result = OpenCms.getSystemInfo().getAbsoluteRfsPathRelativeToWebApplication(result); if (result.endsWith(File.separator)) { // ensure export path does NOT end with a File.separator result = result.substring(0, result.length() - 1); } return result; } /** * Returns a normalized rfs prefix.<p> * * Replacing macros and normalizing the path.<p> * * @param rfsPrefix the prefix to normalize * * @return the normalized rfs prefix */ protected String normalizeRfsPrefix(String rfsPrefix) { String result = insertContextStrings(rfsPrefix); if (!isValidURL(result)) { result = CmsFileUtil.normalizePath(result, '/'); } result = CmsFileUtil.normalizePath(result, '/'); if (CmsResource.isFolder(result)) { // ensure prefix does NOT end with a folder '/' result = result.substring(0, result.length() - 1); } return result; } /** * Reads the resource with the given URI.<p> * * @param cms the current CMS context * @param uri the URI to check * * @return the resource export data * * @throws CmsException if soemthing goes wrong */ protected CmsStaticExportData readResource(CmsObject cms, String uri) throws CmsException { CmsResource resource = null; try { resource = cms.readResource(uri); } catch (CmsVfsResourceNotFoundException e) { String urlName = CmsResource.getName(uri).replaceAll("/$", ""); CmsUUID id = cms.readIdForUrlName(urlName); if (id == null) { throw e; } resource = cms.readResource(id); //String parent = CmsResource.getParentFolder(uri); //resource = cms.readDefaultFile(parent); } return new CmsStaticExportData(uri, null, resource, null); } /** * Scrubs all the "export" folders.<p> * * @param report an I_CmsReport instance to print output message, or null to write messages to the log file */ protected void scrubExportFolders(I_CmsReport report) { if (report != null) { report.println( Messages.get().container(Messages.RPT_DELETING_EXPORT_FOLDERS_BEGIN_0), I_CmsReport.FORMAT_HEADLINE); } synchronized (m_lockScrubExportFolders) { int count = 0; Integer size = new Integer(m_rfsRules.size() + 1); // default case String exportFolderName = CmsFileUtil.normalizePath(m_staticExportPath + '/'); try { File exportFolder = new File(exportFolderName); // check if export file exists, if so delete it if (exportFolder.exists() && exportFolder.canWrite()) { CmsFileUtil.purgeDirectory(exportFolder); } count++; if (report != null) { report.println( Messages.get().container( Messages.RPT_DELETE_EXPORT_FOLDER_3, new Integer(count), size, exportFolderName), I_CmsReport.FORMAT_NOTE); } else { // write log message if (LOG.isInfoEnabled()) { LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MAIN_SE_FOLDER_1, exportFolderName)); } } } catch (Throwable t) { // ignore, nothing to do about the if (LOG.isWarnEnabled()) { LOG.warn(Messages.get().getBundle().key(Messages.LOG_FOLDER_DELETION_FAILED_1, exportFolderName), t); } } // iterate over the rules Iterator<CmsStaticExportRfsRule> it = m_rfsRules.iterator(); while (it.hasNext()) { CmsStaticExportRfsRule rule = it.next(); exportFolderName = CmsFileUtil.normalizePath(rule.getExportPath() + '/'); try { File exportFolder = new File(exportFolderName); // check if export file exists, if so delete it if (exportFolder.exists() && exportFolder.canWrite()) { CmsFileUtil.purgeDirectory(exportFolder); } count++; if (report != null) { report.println( Messages.get().container( Messages.RPT_DELETE_EXPORT_FOLDER_3, new Integer(count), size, exportFolderName), I_CmsReport.FORMAT_NOTE); } else { // write log message if (LOG.isInfoEnabled()) { LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MAIN_SE_FOLDER_1, exportFolderName)); } } } catch (Throwable t) { // ignore, nothing to do about the if (LOG.isWarnEnabled()) { LOG.warn( Messages.get().getBundle().key(Messages.LOG_FOLDER_DELETION_FAILED_1, exportFolderName), t); } } } } if (report != null) { report.println( Messages.get().container(Messages.RPT_DELETING_EXPORT_FOLDERS_END_0), I_CmsReport.FORMAT_HEADLINE); } } /** * Writes a resource to the given export path with the given rfs name and the given content.<p> * * @param req the current request * @param exportPath the path to export the resource * @param rfsName the rfs name * @param resource the resource * @param content the content * * @throws CmsException if something goes wrong */ protected void writeResource( HttpServletRequest req, String exportPath, String rfsName, CmsResource resource, byte[] content) throws CmsException { String exportFileName = CmsFileUtil.normalizePath(exportPath + rfsName); // make sure all required parent folder exist createExportFolder(exportPath, rfsName); // generate export file instance and output stream File exportFile = new File(exportFileName); // write new exported file content try { FileOutputStream exportStream = new FileOutputStream(exportFile); exportStream.write(content); exportStream.close(); // log export success if (LOG.isInfoEnabled()) { LOG.info(Messages.get().getBundle().key( Messages.LOG_STATIC_EXPORTED_2, resource.getRootPath(), exportFileName)); } } catch (Throwable t) { throw new CmsStaticExportException( Messages.get().container(Messages.ERR_OUTPUT_STREAM_1, exportFileName), t); } // update the file with the modification date from the server if (req != null) { Long dateLastModified = (Long)req.getAttribute(CmsRequestUtil.HEADER_OPENCMS_EXPORT); if ((dateLastModified != null) && (dateLastModified.longValue() != -1)) { exportFile.setLastModified((dateLastModified.longValue() / 1000) * 1000); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_SET_LAST_MODIFIED_2, exportFile.getName(), new Long((dateLastModified.longValue() / 1000) * 1000))); } } } else { // otherwise take the last modification date form the OpenCms resource exportFile.setLastModified((resource.getDateLastModified() / 1000) * 1000); } } /** * Returns the map of vfs exportnames with exportname as key and the vfs folder path as value.<p> * * @return the map of vfs exportnames with exportname as key and the vfs folder path as value */ private Map<String, String> computeVfsExportnames() { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_START_0)); } List<CmsResource> resources; CmsObject cms = null; try { // this will always be in the root site cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); resources = cms.readResourcesWithProperty(CmsPropertyDefinition.PROPERTY_EXPORTNAME); synchronized (m_lockSetExportnames) { Map<String, String> exportnameResources = new HashMap<String, String>(); for (int i = 0, n = resources.size(); i < n; i++) { CmsResource res = resources.get(i); try { String foldername = res.getRootPath(); String exportname = cms.readPropertyObject( foldername, CmsPropertyDefinition.PROPERTY_EXPORTNAME, false).getValue(); if (exportname != null) { if (exportname.charAt(exportname.length() - 1) != '/') { exportname = exportname + "/"; } if (exportname.charAt(0) != '/') { exportname = "/" + exportname; } // export name has to be system-wide unique // the folder name is a root path exportnameResources.put(exportname, foldername); } } catch (CmsException e) { // should never happen, folder will not be added LOG.error(e.getLocalizedMessage(), e); } } return Collections.unmodifiableMap(exportnameResources); } } catch (CmsException e) { // should never happen, no resources will be added at all LOG.error(e.getLocalizedMessage(), e); return Collections.emptyMap(); } } }