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