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