org.opencms.staticexport.CmsStaticExportManager Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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.monitor.CmsMemoryMonitor;
import org.opencms.report.CmsLogReport;
import org.opencms.report.I_CmsReport;
import org.opencms.security.CmsSecurityException;
import org.opencms.site.CmsSite;
import org.opencms.site.CmsSiteManagerImpl;
import org.opencms.staticexport.CmsExportname.CmsExportNameComparator;
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.
*
* @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;
/**
* If the property 'secure' is set to this value,
* the resource will be delivered through http and https depending on the link source.
*/
public static final String SECURE_PROPERTY_VALUE_BOTH = "both";
/** 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;
/** CMS context with admin permissions. */
private CmsObject m_adminCms;
/** Cache for the export links. */
private Map m_cacheExportLinks;
/** Cache for the export uris. */
private Map m_cacheExportUris;
/** Cache for the online links. */
private Map m_cacheOnlineLinks;
/** Cache for the secure links. */
private Map 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 m_exportFolders;
/** The additional http headers for the static export. */
private List m_exportHeaders;
/** List of all resources that have the "exportname" property set: <system-wide unique export name, root path>. */
private Map m_exportnameResources;
/** Indicates if true
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 m_exportRules;
/** List of export suffixes where the "export" property default is always true
. */
private List 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 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.
*
*/
public CmsStaticExportManager() {
m_lockCmsEvent = new Object();
m_lockScrubExportFolders = new Object();
m_lockSetExportnames = new Object();
m_exportSuffixes = new ArrayList();
m_exportFolders = new ArrayList();
m_exportHeaders = new ArrayList();
m_rfsRules = new ArrayList();
m_exportRules = new ArrayList();
m_exportTmpRule = new CmsStaticExportExportRule("", "");
m_rfsTmpRule = new CmsStaticExportRfsRule("", "", "", "", "", "", null, null);
m_fullStaticExport = false;
}
/**
* Creates unique, valid RFS name for the given filename that contains
* a coded version of the given parameters, with the given file extension appended.
*
* Adapted from CmsFileUtil.getRfsPath().
*
* @param filename the base file name
* @param extension the extension to use
* @param parameters the parameters to code in the result file name
*
* @return a unique, valid RFS name for the given parameters
*
* @see org.opencms.staticexport.CmsStaticExportManager
*/
public static String getRfsPath(String filename, String extension, String parameters) {
boolean appendSlash = false;
if (filename.endsWith("/")) {
appendSlash = true;
filename = filename.substring(0, filename.length() - 1);
}
StringBuffer buf = new StringBuffer(128);
buf.append(filename);
buf.append('_');
int h = parameters.hashCode();
// ensure we do have a positive id value
buf.append(h > 0 ? h : -h);
buf.append(extension);
if (appendSlash) {
buf.append("/");
}
return buf.toString();
}
/**
* Returns the real file system name plus the default file name.
*
* @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.
*
* @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.
*
* @param regex the regex to add
*/
public void addExportRuleRegex(String regex) {
m_exportTmpRule.addModifiedResource(regex);
}
/**
* Adds a export uri to the latest export rule.
*
* @param exportUri the export uri to add
*/
public void addExportRuleUri(String exportUri) {
m_exportTmpRule.addExportResourcePattern(exportUri);
}
/**
* Adds a new rfs rule to the configuration.
*
* @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 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.
*
* @param regex the regex to add
*/
public void addRfsRuleSystemRes(String regex) {
m_rfsTmpRule.addRelatedSystemRes(regex);
}
/**
* Caches a calculated online link.
*
* @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.
*
* @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 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.
*
* @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.isDetailPage()) {
rfsName = CmsStringUtil.joinPaths(data.getRfsName(), CmsStaticExportManager.DEFAULT_FILE);
} else 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));
}
boolean usesSecureSite = (req != null) && OpenCms.getSiteManager().usesSecureSite(req);
CmsContextInfo contextInfo = new CmsContextInfo(
cms.getRequestContext().getCurrentUser(),
cms.getRequestContext().getCurrentProject(),
vfsName,
cms.getRequestContext().getRequestMatcher(),
siteRoot,
usesSecureSite,
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 locales = OpenCms.getLocaleManager().getDefaultLocales(exportCms, vfsName);
boolean exported = false;
boolean matched = false;
// iterate over all rules
Iterator 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.
*
* @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 eventData = new HashMap();
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 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.
*
* @return the accept-charset header
*/
public String getAcceptCharsetHeader() {
return m_acceptCharsetHeader;
}
/**
* Returns the accept-language header used for internal requests.
*
* @return the accept-language header
*/
public String getAcceptLanguageHeader() {
return m_acceptLanguageHeader;
}
/**
* Returns a cached link for the given vfs name.
*
* @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.
*
* @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.
*
* @return "true"
or "false"
*/
public String getDefault() {
return String.valueOf(m_exportPropertyDefault);
}
/**
* Returns the current default charset header.
*
* @return the current default charset header
*/
public String getDefaultAcceptCharsetHeader() {
return m_defaultAcceptCharsetHeader;
}
/**
* Returns the current default locale header.
*
* @return the current default locale header
*/
public String getDefaultAcceptLanguageHeader() {
return m_defaultAcceptLanguageHeader;
}
/**
* Returns the default prefix for exported links in the "real" file system.
*
* @return the default prefix for exported links in the "real" file system
*/
public String getDefaultRfsPrefix() {
return m_rfsPrefix;
}
/**
* Returns the number of stored backups.
*
* @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.
*
* @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.
*
* @return "true"
or "false"
*/
public String getExportEnabled() {
return String.valueOf(m_staticExportEnabled);
}
/**
* Returns the current folder matcher.
*
* @return the current folder matcher
*/
public CmsExportFolderMatcher getExportFolderMatcher() {
return m_exportFolderMatcher;
}
/**
* Returns list of resources patterns which are part of the export.
*
* @return the of resources patterns which are part of the export.
*/
public List getExportFolderPatterns() {
return Collections.unmodifiableList(m_exportFolders);
}
/**
* Returns specific http headers for the static export.
*
* If the header Cache-Control
is set, OpenCms will not use its default headers.
*
* @return the list of http export headers
*/
public List 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.
*
* @return a map of export names
*/
public Map getExportnames() {
if (m_exportnameResources == null) {
try {
TreeMap sort = new TreeMap(new CmsExportNameComparator());
sort.putAll(computeVfsExportnames());
m_exportnameResources = sort;
} catch (Throwable t) {
LOG.error(t.getMessage(), t);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_FINISHED_0));
}
return Collections.unmodifiableMap(m_exportnameResources);
}
/**
* Returns the export path for the static export, that is the folder where the
* static exported resources will be written to.
*
* The returned value will be a directory like prefix. The value is configured
* in the opencms-importexport.xml
configuration file. An optimization
* of the configured value will be performed, where all relative path information is resolved
* (for example /export/../static
will be resolved to /export
.
* Moreover, if the configured path ends with a /
, this will be cut off
* (for example /export/
becomes /export
.
*
* This is resource name based, and based on the rfs-rules defined in the
* opencms-importexport.xml
configuration file.
*
* @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 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.
*
* This is required only to serialize the configuration again exactly as it was configured.
* This method should not be used otherwise. Use {@link #getExportPath(String)}
* to obtain the export path to use when exporting.
*
* @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.
*
* @return true if the default value for the resource property "export" is true
*/
public boolean getExportPropertyDefault() {
return m_exportPropertyDefault;
}
/**
* Returns the export Rules.
*
* @return the export Rules
*/
public List getExportRules() {
return Collections.unmodifiableList(m_exportRules);
}
/**
* Gets the list of resource suffixes which will be exported by default.
*
* @return list of resource suffixes
*/
public List getExportSuffixes() {
return m_exportSuffixes;
}
/**
* Returns the export URL used for internal requests for exporting resources that require a
* request / response (like JSP).
*
* @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.
*
* This is required only to serialize the configuration again exactly as it was configured.
* This method should not be used otherwise. Use {@link #getExportUrl()}
* to obtain the export path to use when exporting.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* If not set, a new {@link CmsAfterPublishStaticExportHandler}
is created and returned.
*
* @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.
*
* If not set, a new {@link CmsDefaultLinkSubstitutionHandler}
is created and returned.
*
* @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.
*
* @return "true"
or "false"
*/
public String getPlainExportOptimization() {
return String.valueOf(m_quickPlainExport);
}
/**
* Returns true if the quick plain export is enabled.
*
* @return true if the quick plain export is enabled
*/
public boolean getQuickPlainExport() {
return m_quickPlainExport;
}
/**
* Gets the relative links value as a string representation.
*
* @return "true"
or "false"
*/
public String getRelativeLinks() {
return String.valueOf(m_exportRelativeLinks);
}
/**
* Returns the remote address used for internal requests.
*
* @return the remote address
*/
public String getRemoteAddr() {
return m_remoteAddr;
}
/**
* Returns the remote address.
*
* @return the remote address
*/
public String getRemoteAddress() {
return m_remoteAddr;
}
/**
* Returns the static export rfs name for a given vfs resource.
*
* @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, String)
*/
public String getRfsName(CmsObject cms, String vfsName) {
return getRfsName(cms, vfsName, null, null);
}
/**
* Returns the static export rfs name for a given vfs resource where the link to the
* resource includes request parameters.
*
* @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
* @param targetDetailPage the target detail page to use
*
* @return the static export rfs name for a give vfs resource
*/
public String getRfsName(CmsObject cms, String vfsName, String parameters, String targetDetailPage) {
String rfsName;
try {
CmsResource vfsRes = null;
if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_4_SERVLET_ACCESS) {
// Accessing the ADEManager during setup may not work.
try {
vfsRes = cms.readResource(vfsName);
I_CmsDetailPageFinder finder = OpenCms.getADEManager().getDetailPageFinder();
String detailPage = finder.getDetailPage(
cms,
vfsRes.getRootPath(),
cms.getRequestContext().getUri(),
targetDetailPage);
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 = 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 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.
*
* The returned value will be a directory like prefix. The value is configured
* in the opencms-importexport.xml
configuration file. An optimization
* of the configured value will be performed, where all relative path information is resolved
* (for example /export/../static
will be resolved to /export
.
* Moreover, if the configured path ends with a /
, this will be cut off
* (for example /export/
becomes /export
.
*
* This is resource name based, and based on the rfs-rules defined in the
* opencms-importexport.xml
configuration file.
*
* @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 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.
*
* This is required only to serialize the configuration again exactly as it was configured.
* This method should not be used otherwise. Use {@link #getRfsPrefix(String)}
* to obtain the rfs prefix to use for the exported links.
*
* @return the original configured prefix for exported links in the "real" file
*/
public String getRfsPrefixForConfiguration() {
return m_rfsPrefixConfigured;
}
/**
* Returns the rfs Rules.
*
* @return the rfs Rules
*/
public List getRfsRules() {
return Collections.unmodifiableList(m_rfsRules);
}
/**
* Returns the vfs name of the test resource.
*
* @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.
*
* @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 {@link #getRfsName(CmsObject, String)}
.
*
* Returns null
if no matching VFS resource can be found for the given RFS name.
*
* @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 null
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.
*
* The RFS name must not contain the RFS prefix.
*
* @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();
CmsSite currentSite = OpenCms.getSiteManager().getSiteForSiteRoot(storedSiteRoot);
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 exportnameMapping = getExportnames();
// in case of folders, remove the trailing slash and in case of files, remove the filename and trailing slash
while (true) {
// exportnameResources are only folders!
String expName = exportnameMapping.get(new CmsExportname(path + "/", currentSite));
if (expName == null) {
expName = exportnameMapping.get(new CmsExportname(path + "/", null));
}
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.
*
* The returned value will be a directory like prefix. The value is configured
* in the opencms-importexport.xml
configuration file. An optimization
* of the configured value will be performed, where all relative path information is resolved
* (for example /opencms/../mycms
will be resolved to /mycms
.
* Moreover, if the configured path ends with a /
, this will be cut off
* (for example /opencms/
becomes /opencms
.
*
* @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.
*
* This is required only to serialize the configuration again exactly as it was configured.
* This method should not be used otherwise. Use {@link #getVfsPrefix()}
* to obtain the VFS prefix to use for the internal links.
*
* @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.
*
* @param cms an OpenCms context object
*/
public void initialize(CmsObject cms) {
m_adminCms = 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 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));
}
}
m_cacheOnlineLinks = CmsMemoryMonitor.createLRUCacheMap(2048);
OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheOnlineLinks", m_cacheOnlineLinks);
m_cacheExportUris = CmsMemoryMonitor.createLRUCacheMap(2048);
OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportUris", m_cacheExportUris);
m_cacheSecureLinks = CmsMemoryMonitor.createLRUCacheMap(2048);
OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheSecureLinks", m_cacheSecureLinks);
m_cacheExportLinks = CmsMemoryMonitor.createLRUCacheMap(2048);
OpenCms.getMemoryMonitor().register(this.getClass().getName() + ".m_cacheExportLinks", m_cacheExportLinks);
// 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.
*
* 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.
*
* 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.
*
* @param cms the current users OpenCms context
* @param vfsName the VFS resource name to check
*
* @return true
if static export is required for the given VFS resource
*/
public boolean isExportLink(CmsObject cms, String vfsName) {
LOG.info("isExportLink? " + vfsName);
if (!isStaticExportEnabled()) {
return false;
}
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.
*
* @return true if the export process is a full static export
*/
public boolean isFullStaticExport() {
return m_fullStaticExport;
}
/**
* Returns true
if the given VFS resource should be transported through a secure channel.
*
* The secure mode is only checked in the "Online" project.
* If the given OpenCms context is currently not in the "Online" project,
* false
is returned.
*
* The given resource is read from the site root of the provided OpenCms context.
*
* @param cms the current users OpenCms context
* @param vfsName the VFS resource name to check
*
* @return true
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) {
return isSecureLink(cms, vfsName, false);
}
/**
* Returns true
if the given VFS resource should be transported through a secure channel.
*
* The secure mode is only checked in the "Online" project.
* If the given OpenCms context is currently not in the "Online" project,
* false
is returned.
*
* The given resource is read from the site root of the provided OpenCms context.
*
* @param cms the current users OpenCms context
* @param vfsName the VFS resource name to check
* @param fromSecure true
if the link source is delivered secure
*
* @return true
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, boolean fromSecure) {
if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) {
return false;
}
String cacheKey = OpenCms.getStaticExportManager().getCacheKey(cms.getRequestContext().getSiteRoot(), vfsName);
String secureResource = OpenCms.getStaticExportManager().getCacheSecureLinks().get(cacheKey);
if (secureResource == null) {
CmsObject cmsForReadingProperties = cms;
try {
// the link target resource may not be readable by the current user, so we use a CmsObject with admin permissions
// to read the "secure" property
CmsObject adminCms = OpenCms.initCmsObject(m_adminCms);
adminCms.getRequestContext().setSiteRoot(cms.getRequestContext().getSiteRoot());
adminCms.getRequestContext().setCurrentProject(cms.getRequestContext().getCurrentProject());
adminCms.getRequestContext().setRequestTime(cms.getRequestContext().getRequestTime());
cmsForReadingProperties = adminCms;
} catch (Exception e) {
LOG.error("Could not initialize CmsObject in isSecureLink:" + e.getLocalizedMessage(), e);
}
try {
secureResource = cmsForReadingProperties.readPropertyObject(
vfsName,
CmsPropertyDefinition.PROPERTY_SECURE,
true).getValue();
if (CmsStringUtil.isEmptyOrWhitespaceOnly(secureResource)) {
secureResource = "false";
}
// only cache result if read was successfull
OpenCms.getStaticExportManager().getCacheSecureLinks().put(cacheKey, secureResource);
} catch (CmsVfsResourceNotFoundException e) {
secureResource = SECURE_PROPERTY_VALUE_BOTH;
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
return false;
}
}
return Boolean.parseBoolean(secureResource)
|| (fromSecure && SECURE_PROPERTY_VALUE_BOTH.equals(secureResource));
}
/**
* Returns true
if the given VFS resource that is located under the
* given site root should be transported through a secure channel.
*
* @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 true
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) {
return isSecureLink(cms, vfsName, siteRoot, false);
}
/**
* Returns true
if the given VFS resource should be transported through a secure channel.
*
* The secure mode is only checked in the "Online" project.
* If the given OpenCms context is currently not in the "Online" project,
* false
is returned.
*
* The given resource is read from the site root of the provided OpenCms context.
*
* @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
* @param fromSecure true
if the link source is delivered secure
*
* @return true
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, String siteRoot, boolean fromSecure) {
if (siteRoot == null) {
return isSecureLink(cms, vfsName, fromSecure);
}
// 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, fromSecure);
} finally {
cms.getRequestContext().setSiteRoot(storedSiteRoot);
}
}
/**
* Returns true if the static export is enabled.
*
* @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.
*
* @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.
*
* @return true
if using temporary directories
*/
public boolean isUseTempDir() {
return m_useTempDirs;
}
/**
* Returns true if the links in the static export should be relative.
*
* @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 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.
*
* @param value accept-language header value
*/
public void setAcceptCharsetHeader(String value) {
m_acceptCharsetHeader = value;
}
/**
* Sets the accept-language header value.
*
* @param value accept-language header value
*/
public void setAcceptLanguageHeader(String value) {
m_acceptLanguageHeader = value;
}
/**
* Sets the default property value.
*
* @param value must be true
or false
*/
public void setDefault(String value) {
m_exportPropertyDefault = Boolean.valueOf(value).booleanValue();
}
/**
* Sets the number of backups for the static export.
*
* @param backup number of backups
*/
public void setExportBackups(String backup) {
m_staticExportBackups = new Integer(backup);
}
/**
* Sets the export enabled value.
*
* @param value must be true
or false
*/
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.
*
* @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.
*
* The format of the headers must be "header:value".
*
* @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.
*
* @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.
*
* @param suffix the suffix to add to the list.
*/
public void setExportSuffix(String suffix) {
m_exportSuffixes.add(suffix.toLowerCase());
}
/**
* Sets the export url.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @param value must be true
or false
*/
public void setPlainExportOptimization(String value) {
m_quickPlainExport = Boolean.valueOf(value).booleanValue();
}
/**
* Sets the relative links value.
*
* @param value must be true
or false
*/
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.
*
* @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.
*
* @param rfsPrefix the prefix for exported links in the "real" file system
*/
public void setRfsPrefix(String rfsPrefix) {
m_rfsPrefixConfigured = rfsPrefix;
}
/**
* Sets the test resource.
*
* @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.
*
* @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.
*
* This is required since there may still be a thread running when the system is being shut down.
*/
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.
*
* @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.
*
* @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.
*
* @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.
*
* @return the cacheExportLinks
*/
protected Map getCacheExportLinks() {
return m_cacheExportLinks;
}
/**
* Returns the cacheSecureLinks.
*
* @return the cacheSecureLinks
*/
protected Map getCacheSecureLinks() {
return m_cacheSecureLinks;
}
/**
* Returns the export data for a requested resource, if null is returned no export is required.
*
* @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 = null;
String siteRoot = OpenCms.getSiteManager().getSiteRoot(cms.getRequestContext().getSiteRoot());
if (siteRoot != null) {
data = m_cacheExportUris.get(siteRoot + ":" + rfsName);
} else {
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);
}
if (data.getResource() != null) {
siteRoot = OpenCms.getSiteManager().getSiteRoot(data.getResource().getRootPath());
}
if (siteRoot != null) {
m_cacheExportUris.put(siteRoot + ":" + rfsName, data);
} else {
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.
*
* @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.
*
* @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 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.
*
* @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.
*
* @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 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.
*
* @param inputString The String to check can be null
*
* @return true
if the String is not null
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.
*
* Replacing macros, normalizing the path and taking care of relative paths.
*
* @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.
*
* Replacing macros and normalizing the path.
*
* @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.
*
* @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;
boolean isDetailPage = false;
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);
isDetailPage = true;
//String parent = CmsResource.getParentFolder(uri);
//resource = cms.readDefaultFile(parent);
}
CmsStaticExportData result = new CmsStaticExportData(uri, null, resource, null);
result.setIsDetailPage(isDetailPage);
return result;
}
/**
* Scrubs all the "export" folders.
*
* @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 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.
*
* @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.
*
* @return the map of vfs exportnames with exportname as key and the vfs folder path as value
*/
private Map computeVfsExportnames() {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXPORTNAME_PROP_START_0));
}
CmsSiteManagerImpl sm = OpenCms.getSiteManager();
List 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 exportnameResources = new HashMap();
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();
CmsSite site = sm.getSiteForRootPath(foldername);
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(new CmsExportname(exportname, site), 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();
}
}
}