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