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.site;
029
030import com.alkacon.simapi.CmykJpegReader.StringUtil;
031
032import org.opencms.configuration.CmsConfigurationException;
033import org.opencms.configuration.CmsSitesConfiguration;
034import org.opencms.db.CmsPublishedResource;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsPropertyDefinition;
038import org.opencms.file.CmsResource;
039import org.opencms.file.CmsResourceFilter;
040import org.opencms.main.CmsContextInfo;
041import org.opencms.main.CmsEvent;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsLog;
044import org.opencms.main.CmsRuntimeException;
045import org.opencms.main.I_CmsEventListener;
046import org.opencms.main.OpenCms;
047import org.opencms.main.OpenCmsCore;
048import org.opencms.security.CmsOrganizationalUnit;
049import org.opencms.security.CmsPermissionSet;
050import org.opencms.security.CmsRole;
051import org.opencms.util.CmsFileUtil;
052import org.opencms.util.CmsPath;
053import org.opencms.util.CmsStringUtil;
054import org.opencms.util.CmsUUID;
055
056import java.net.URI;
057import java.net.URISyntaxException;
058import java.util.ArrayList;
059import java.util.Collection;
060import java.util.Collections;
061import java.util.Comparator;
062import java.util.HashMap;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.LinkedHashMap;
066import java.util.List;
067import java.util.Map;
068import java.util.Set;
069import java.util.SortedMap;
070
071import javax.servlet.http.HttpServletRequest;
072import javax.servlet.http.HttpSession;
073
074import org.apache.commons.logging.Log;
075
076import com.google.common.base.Optional;
077import com.google.common.collect.Lists;
078import com.google.common.collect.Maps;
079import com.google.common.collect.Sets;
080
081/**
082 * Manages all configured sites in OpenCms.<p>
083 *
084 * To obtain the configured site manager instance, use {@link OpenCms#getSiteManager()}.<p>
085 *
086 * @since 7.0.2
087 */
088public final class CmsSiteManagerImpl implements I_CmsEventListener {
089
090    /**
091     * Holds data for the alternative site root mappings.
092     */
093    private static class AlternativeSiteData {
094
095        /** Map from site roots as strings to the corresponding alternative site roots. */
096        private Map<CmsPath, CmsSite> m_alternativeSites = new HashMap<>();
097
098        /** Site roots for the alternative site data. */
099        private Set<String> m_siteRoots = new HashSet<>();
100
101        /**
102         * Creates a new instance from the alternative site root mappings of the given site.
103         *
104         * @param normalSites the normal sites
105         */
106        public AlternativeSiteData(Collection<CmsSite> normalSites) {
107
108            for (CmsSite site : normalSites) {
109                if (site.getAlternativeSiteRootMapping().isPresent()) {
110                    CmsSite extensionSite = site.createAlternativeSiteRootSite();
111                    CmsPath key = new CmsPath(extensionSite.getSiteRoot());
112                    m_alternativeSites.put(key, extensionSite);
113                    m_siteRoots.add(key.asString());
114                }
115
116            }
117        }
118
119        /**
120         * Gets the site for the given root path, or null if no site for that path is found.
121         *
122         * @param path a root path
123         * @return the site for the root path, or null
124         */
125        public CmsSite getSiteForRootPath(String path) {
126
127            for (Map.Entry<CmsPath, CmsSite> entry : m_alternativeSites.entrySet()) {
128                CmsPath key = entry.getKey();
129                if (key.isPrefixOfStr(path)) {
130                    return entry.getValue();
131                }
132            }
133            return null;
134        }
135
136        /**
137         * Gets the site for the given site root.
138         *
139         * @param path a site root
140         * @return the site for the site root
141         */
142        public CmsSite getSiteForSiteRoot(String path) {
143
144            CmsPath key = new CmsPath(path);
145            CmsSite result = m_alternativeSites.get(key);
146            return result;
147        }
148
149        /**
150         * Gets the site roots for the alternative site root mappings.
151         *
152         * @return the site roots
153         */
154        public Set<String> getSiteRoots() {
155
156            return Collections.unmodifiableSet(m_siteRoots);
157        }
158
159    }
160
161    /** The default shared folder name. */
162    public static final String DEFAULT_SHARED_FOLDER = "shared";
163
164    /**
165     * The VFS root path to the system shared folder, where shared content that belongs to modules,
166     * and that should not be edited by normal editors can be stored.
167     * The folder is searched in the gallery search when shared folders should be searched.
168     */
169    public static final String PATH_SYSTEM_SHARED_FOLDER = "/system/shared/";
170
171    /** A placeholder for the title of the shared folder. */
172    public static final String SHARED_FOLDER_TITLE = "%SHARED_FOLDER%";
173
174    /** Path to config template. */
175    public static final String WEB_SERVER_CONFIG_CONFIGTEMPLATE = "configtemplate";
176
177    /**prefix for files. */
178    public static final String WEB_SERVER_CONFIG_FILENAMEPREFIX = "filenameprefix";
179
180    /**Path to write logs to. */
181    public static final String WEB_SERVER_CONFIG_LOGGINGDIR = "loggingdir";
182
183    /** Path to secure template. */
184    public static final String WEB_SERVER_CONFIG_SECURETEMPLATE = "securetemplate";
185
186    /** Path to target. */
187    public static final String WEB_SERVER_CONFIG_TARGETPATH = "targetpath";
188
189    /** Path of webserver script.*/
190    public static final String WEB_SERVER_CONFIG_WEBSERVERSCRIPT = "webserverscript";
191
192    /** The static log object for this class. */
193    private static final Log LOG = CmsLog.getLog(CmsSiteManagerImpl.class);
194
195    /** The path to the "/sites/" folder. */
196    private static final String SITES_FOLDER = "/sites/";
197
198    /** The length of the "/sites/" folder plus 1. */
199    private static final int SITES_FOLDER_POS = SITES_FOLDER.length() + 1;
200
201    /** A list of additional site roots, that is site roots that are not below the "/sites/" folder. */
202    private List<String> m_additionalSiteRoots;
203
204    /** Data for the alternative site root rules. */
205    private volatile AlternativeSiteData m_alternativeSiteData = new AlternativeSiteData(new ArrayList<>());
206
207    /**Map with webserver scripting parameter. */
208    private Map<String, String> m_apacheConfig;
209
210    /**CmsObject.*/
211    private CmsObject m_clone;
212
213    /** The default site root. */
214    private CmsSite m_defaultSite;
215
216    /** The default URI. */
217    private String m_defaultUri;
218
219    /** Indicates if the configuration is finalized (frozen). */
220    private boolean m_frozen;
221
222    /**Is the publish listener already set? */
223    private boolean m_isListenerSet;
224
225    /**Old style secure server allowed? */
226    private boolean m_oldStyleSecureServer;
227
228    /**Site which are only available for offline project. */
229    private List<CmsSite> m_onlyOfflineSites;
230
231    /** The shared folder name. */
232    private String m_sharedFolder;
233
234    /** Contains all configured site matchers in a list for direct access. */
235    private List<CmsSiteMatcher> m_siteMatchers;
236
237    /** Maps site matchers to sites. */
238    private Map<CmsSiteMatcher, CmsSite> m_siteMatcherSites;
239
240    /** Maps site roots to sites. */
241    private Map<String, CmsSite> m_siteRootSites;
242
243    /**Map from CmsUUID to CmsSite.*/
244    private Map<CmsUUID, CmsSite> m_siteUUIDs;
245
246    /** The workplace site matchers. */
247    private List<CmsSiteMatcher> m_workplaceMatchers;
248
249    /** The workplace servers. */
250    private Map<String, CmsSSLMode> m_workplaceServers;
251
252    /**
253     * Creates a new CmsSiteManager.<p>
254     *
255     */
256    public CmsSiteManagerImpl() {
257
258        m_siteMatcherSites = new HashMap<CmsSiteMatcher, CmsSite>();
259        m_siteRootSites = new HashMap<String, CmsSite>();
260        m_additionalSiteRoots = new ArrayList<String>();
261        m_workplaceServers = new LinkedHashMap<String, CmsSSLMode>();
262        m_workplaceMatchers = new ArrayList<CmsSiteMatcher>();
263        m_oldStyleSecureServer = true;
264        if (CmsLog.INIT.isInfoEnabled()) {
265            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_START_SITE_CONFIG_0));
266        }
267    }
268
269    /**
270     * Creates a site matcher for an alias read from the configuration.
271     *
272     * @param alias the alias
273     * @param redirect redirection enabled (true/false)
274     * @param offset time offset or empty
275     *
276     * @return the alias site matcher
277     */
278    public static CmsSiteMatcher createAliasSiteMatcher(String alias, String redirect, String offset) {
279
280        long timeOffset = 0;
281        try {
282            timeOffset = Long.parseLong(offset);
283        } catch (Throwable e) {
284            // ignore
285        }
286        CmsSiteMatcher siteMatcher = new CmsSiteMatcher(alias, timeOffset);
287        CmsSiteMatcher.RedirectMode redirectMode = CmsSiteMatcher.RedirectMode.parse(redirect);
288        siteMatcher.setRedirectMode(redirectMode);
289        return siteMatcher;
290    }
291
292    /**
293     * Parses the given string as an URI and returns its host component.
294     *
295     * @param uriStr the URI string
296     * @return the host component, or null if the URI can't be parsed
297     */
298    private static String getHost(String uriStr) {
299
300        try {
301            URI uri = new URI(uriStr);
302            return uri.getHost();
303        } catch (URISyntaxException e) {
304            return null;
305        }
306    }
307
308    /**
309     * Adds a site.<p>
310     *
311     * @param cms the CMS object
312     * @param site the site to add
313     *
314     * @throws CmsException if something goes wrong
315     */
316    public void addSite(CmsObject cms, CmsSite site) throws CmsException {
317
318        // check permissions
319        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
320            // simple unit tests will have runlevel 1 and no CmsObject
321            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
322        }
323
324        validateSiteRoot(site.getSiteRoot());
325
326        // un-freeze
327        m_frozen = false;
328
329        String secureUrl = null;
330        if (site.hasSecureServer()) {
331            secureUrl = site.getSecureUrl();
332        }
333
334        // add the site
335        addSite(
336            site.getUrl(),
337            site.getSiteRoot(),
338            site.getTitle(),
339            Float.toString(site.getPosition()),
340            site.getErrorPage(),
341            Boolean.toString(site.isWebserver()),
342            site.getSSLMode().getXMLValue(),
343            secureUrl,
344            Boolean.toString(site.isExclusiveUrl()),
345            Boolean.toString(site.isExclusiveError()),
346            Boolean.toString(site.usesPermanentRedirects()),
347            Boolean.toString(site.isSubsiteSelectionEnabled()),
348            site.getParameters(),
349            site.getAliases(),
350            site.getAlternativeSiteRootMapping());
351
352        // re-initialize, will freeze the state when finished
353        initialize(cms);
354        OpenCms.writeConfiguration(CmsSitesConfiguration.class);
355    }
356
357    /**
358     * Adds a new CmsSite to the list of configured sites,
359     * this is only allowed during configuration.<p>
360     *
361     * If this method is called after the configuration is finished,
362     * a <code>RuntimeException</code> is thrown.<p>
363     *
364     * @param server the Server
365     * @param uri the VFS path
366     * @param title the display title for this site
367     * @param position the display order for this site
368     * @param errorPage the URI to use as error page for this site
369     * @param sslMode the SSLMode of the site
370     * @param webserver indicates whether to write the web server configuration for this site or not
371     * @param secureServer a secure server, can be <code>null</code>
372     * @param exclusive if set to <code>true</code>, secure resources will only be available using the configured secure url
373     * @param error if exclusive, and set to <code>true</code> will generate a 404 error,
374     *                             if set to <code>false</code> will redirect to secure URL
375     * @param usePermanentRedirects if set to "true", permanent redirects should be used when redirecting to the secure URL
376     * @param subsiteSelection true if subsite selection should be enabled
377     * @param params the site parameters
378     * @param aliases the aliases for the site
379     * @param alternativeSiteRootMapping an optional alternative site root mapping
380     *
381     * @throws CmsConfigurationException if the site contains a server name, that is already assigned
382     */
383    public void addSite(
384        String server,
385        String uri,
386        String title,
387        String position,
388        String errorPage,
389        String webserver,
390        String sslMode,
391        String secureServer,
392        String exclusive,
393        String error,
394        String usePermanentRedirects,
395        String subsiteSelection,
396        SortedMap<String, String> params,
397        List<CmsSiteMatcher> aliases,
398        java.util.Optional<CmsAlternativeSiteRootMapping> alternativeSiteRootMapping)
399    throws CmsConfigurationException {
400
401        if (m_frozen) {
402            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
403        }
404
405        if (getSiteRoots().contains(uri)) {
406            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_SITE_ALREADY_CONFIGURED_1, uri));
407        }
408
409        if (CmsStringUtil.isEmptyOrWhitespaceOnly(server)) {
410            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_EMPTY_SERVER_URL_0));
411        }
412
413        validateSiteRoot(uri);
414
415        // create a new site object
416        CmsSiteMatcher matcher = new CmsSiteMatcher(server);
417        CmsSite site = new CmsSite(uri, matcher);
418        // set the title
419        site.setTitle(title);
420        // set the position
421        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(position)) {
422            float pos = Float.MAX_VALUE;
423            try {
424                pos = Float.parseFloat(position);
425            } catch (Throwable e) {
426                // m_position will have Float.MAX_VALUE, so this site will appear last
427            }
428            site.setPosition(pos);
429        }
430        // set the error page
431        site.setErrorPage(errorPage);
432        site.setWebserver(Boolean.valueOf(webserver).booleanValue());
433        site.setSSLMode(CmsSSLMode.getModeFromXML(sslMode));
434        if (CmsStringUtil.isNotEmpty(secureServer)) {
435            matcher = new CmsSiteMatcher(secureServer);
436            site.setSecureServer(matcher);
437            site.setExclusiveUrl(Boolean.valueOf(exclusive).booleanValue());
438            site.setExclusiveError(Boolean.valueOf(error).booleanValue());
439            site.setUsePermanentRedirects(Boolean.valueOf(usePermanentRedirects).booleanValue());
440        }
441        site.setSubsiteSelectionEnabled(Boolean.parseBoolean(subsiteSelection));
442
443        // note that Digester first calls the addAliasToConfigSite method.
444        // therefore, the aliases are already set
445        site.setAliases(aliases);
446
447        boolean valid = true;
448        List<CmsSiteMatcher> toAdd = new ArrayList<CmsSiteMatcher>();
449        for (CmsSiteMatcher matcherToAdd : site.getAllMatchers()) {
450            valid = valid & isServerValid(matcherToAdd) & !toAdd.contains(matcherToAdd);
451            toAdd.add(matcherToAdd);
452        }
453
454        if (!valid) {
455            throw new CmsConfigurationException(
456                Messages.get().container(Messages.ERR_DUPLICATE_SERVER_NAME_1, matcher.getUrl()));
457        }
458
459        for (CmsSiteMatcher matcherToAdd : site.getAllMatchers()) {
460            addServer(matcherToAdd, site);
461        }
462
463        site.setParameters(params);
464        site.setAlternativeSiteRootMapping(alternativeSiteRootMapping);
465        m_siteRootSites = new HashMap<String, CmsSite>(m_siteRootSites);
466        m_siteRootSites.put(site.getSiteRoot(), site);
467        if (CmsLog.INIT.isInfoEnabled()) {
468            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SITE_ROOT_ADDED_1, site.toString()));
469        }
470    }
471
472    /**
473     * Adds a new CmsSite to the list of configured sites,
474     * this is only allowed during configuration.<p>
475     *
476     * If this method is called after the configuration is finished,
477     * a <code>RuntimeException</code> is thrown.<p>
478     *
479     * @param server the Server
480     * @param uri the VFS path
481     * @param title the display title for this site
482     * @param position the display order for this site
483     * @param errorPage the URI to use as error page for this site
484     * @param sslMode the SSLMode of the site
485     * @param webserver indicates whether to write the web server configuration for this site or not
486     * @param secureServer a secure server, can be <code>null</code>
487     * @param exclusive if set to <code>true</code>, secure resources will only be available using the configured secure url
488     * @param error if exclusive, and set to <code>true</code> will generate a 404 error,
489     *                             if set to <code>false</code> will redirect to secure URL
490     * @param usePermanentRedirects if set to "true", permanent redirects should be used when redirecting to the secure URL
491     * @param subsiteSelection true if subsite selection should be enabled for this site
492     * @param params the site parameters
493     * @param aliases the aliases
494     * @param alternativeSiteRoot an optional alternative site root mapping
495     *
496     * @throws CmsConfigurationException in case the site was not configured correctly
497     *
498     */
499    public void addSiteInternally(
500        String server,
501        String uri,
502        String title,
503        String position,
504        String errorPage,
505        String webserver,
506        String sslMode,
507        String secureServer,
508        String exclusive,
509        String error,
510        String usePermanentRedirects,
511        String subsiteSelection,
512        SortedMap<String, String> params,
513        List<CmsSiteMatcher> aliases,
514        java.util.Optional<CmsAlternativeSiteRootMapping> alternativeSiteRoot)
515    throws CmsConfigurationException {
516
517        try {
518            addSite(
519                server,
520                uri,
521                title,
522                position,
523                errorPage,
524                webserver,
525                sslMode,
526                secureServer,
527                exclusive,
528                error,
529                usePermanentRedirects,
530                subsiteSelection,
531                params,
532                aliases,
533                alternativeSiteRoot);
534
535        } catch (CmsConfigurationException e) {
536            LOG.error("Error reading definitions. Trying to read without aliases.", e);
537
538            //If this fails, the webserver was defined before ->throw exception
539
540            addSite(
541                server,
542                uri,
543                title,
544                position,
545                errorPage,
546                webserver,
547                sslMode,
548                secureServer,
549                exclusive,
550                error,
551                usePermanentRedirects,
552                subsiteSelection,
553                params,
554                new ArrayList<>(),
555                alternativeSiteRoot); //If the aliases are making problems, just remove thems
556
557        }
558    }
559
560    /**
561     * Adds a workplace server, this is only allowed during configuration.<p>
562     *
563     * @param workplaceServer the workplace server
564     * @param sslmode CmsSSLMode of workplace server
565     */
566    public void addWorkplaceServer(String workplaceServer, String sslmode) {
567
568        if (m_frozen) {
569            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
570        }
571        if (!m_workplaceServers.containsKey(workplaceServer)) {
572            m_workplaceServers.put(workplaceServer, CmsSSLMode.getModeFromXML(sslmode));
573        }
574    }
575
576    /**
577     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
578     */
579    public void cmsEvent(CmsEvent event) {
580
581        try {
582            CmsProject project = getOfflineProject();
583            m_clone.getRequestContext().setCurrentProject(project);
584            List<CmsPublishedResource> res = null;
585
586            List<CmsPublishedResource> foundSites = new ArrayList<CmsPublishedResource>();
587
588            res = m_clone.readPublishedResources(
589                new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID)));
590
591            if (res != null) {
592                for (CmsPublishedResource r : res) {
593                    if (!foundSites.contains(r)) {
594                        if (m_siteUUIDs.containsKey(r.getStructureId())) {
595                            foundSites.add(r);
596                        }
597                    }
598                }
599            }
600            project = m_clone.readProject(CmsProject.ONLINE_PROJECT_ID);
601            m_clone.getRequestContext().setCurrentProject(project);
602            Map<CmsSite, CmsSite> updateMap = new HashMap<CmsSite, CmsSite>();
603
604            for (CmsPublishedResource r : foundSites) {
605                if (m_clone.existsResource(r.getStructureId())) {
606                    //Resource was not deleted
607                    CmsResource siteRoot = m_clone.readResource(r.getStructureId());
608                    if (!m_siteRootSites.containsKey(CmsFileUtil.removeTrailingSeparator(siteRoot.getRootPath()))
609                        | m_onlyOfflineSites.contains(m_siteUUIDs.get(r.getStructureId()))) {
610                        //Site was moved or site root was renamed.. or site was published the first time
611                        CmsSite oldSite = m_siteUUIDs.get(siteRoot.getStructureId());
612                        CmsSite newSite = oldSite.clone();
613                        newSite.setSiteRoot(siteRoot.getRootPath());
614                        updateMap.put(oldSite, newSite);
615                    }
616                }
617            }
618
619            for (CmsSite site : updateMap.keySet()) {
620                updateSite(m_clone, site, updateMap.get(site));
621            }
622        } catch (CmsException e) {
623            LOG.error("Unable to handle publish event", e);
624        }
625
626    }
627
628    /**
629     * Returns all wrong configured sites.<p>
630     *
631     * @param cms CmsObject
632     * @param workplaceMode workplace mode
633     * @return List of CmsSite
634     */
635    public List<CmsSite> getAvailableCorruptedSites(CmsObject cms, boolean workplaceMode) {
636
637        List<CmsSite> res = new ArrayList<CmsSite>();
638        List<CmsSite> visSites = getAvailableSites(cms, workplaceMode);
639        Map<CmsSiteMatcher, CmsSite> allsites = getSites();
640        for (CmsSiteMatcher matcher : allsites.keySet()) {
641            CmsSite site = allsites.get(matcher);
642            if (!visSites.contains(site) & !res.contains(site)) {
643                res.add(site);
644            }
645        }
646        return res;
647    }
648
649    /**
650     * Returns a list of all sites available (visible) for the current user.<p>
651     *
652     * @param cms the current OpenCms user context
653     * @param workplaceMode if true, the root and current site is included for the admin user
654     *                      and the view permission is required to see the site root
655     *
656     * @return a list of all sites available for the current user
657     */
658    public List<CmsSite> getAvailableSites(CmsObject cms, boolean workplaceMode) {
659
660        return getAvailableSites(cms, workplaceMode, cms.getRequestContext().getOuFqn());
661    }
662
663    /**
664     * Returns a list of all {@link CmsSite} instances that are compatible to the given organizational unit.<p>
665     *
666     * @param cms the current OpenCms user context
667     * @param workplaceMode if true, the root and current site is included for the admin user
668     *                      and the view permission is required to see the site root
669     * @param showShared if the shared folder should be shown
670     * @param ouFqn the organizational unit
671     *
672     * @return a list of all site available for the current user
673     */
674    public List<CmsSite> getAvailableSites(CmsObject cms, boolean workplaceMode, boolean showShared, String ouFqn) {
675
676        return getAvailableSites(cms, workplaceMode, showShared, ouFqn, null);
677    }
678
679    /**
680     * Returns a list of all {@link CmsSite} instances that are compatible to the given organizational unit.<p>
681     *
682     * @param cms the current OpenCms user context
683     * @param workplaceMode if true, the root and current site is included for the admin user
684     *                      and the view permission is required to see the site root
685     * @param showShared if the shared folder should be shown
686     * @param ouFqn the organizational unit
687     * @param filterMode The CmsSLLMode to filter, null if no filter
688     *
689     * @return a list of all site available for the current user
690     */
691    public List<CmsSite> getAvailableSites(
692        CmsObject cms,
693        boolean workplaceMode,
694        boolean showShared,
695        String ouFqn,
696        CmsSSLMode filterMode) {
697
698        List<String> siteroots = new ArrayList<String>(m_siteMatcherSites.size() + 1);
699        Map<String, CmsSiteMatcher> siteServers = new HashMap<String, CmsSiteMatcher>(m_siteMatcherSites.size() + 1);
700        List<CmsSite> result = new ArrayList<CmsSite>(m_siteMatcherSites.size() + 1);
701
702        for (CmsSite mainSite : m_siteMatcherSites.values()) {
703            List<CmsSite> sitesToProcess = new ArrayList<>();
704            sitesToProcess.add(mainSite);
705            CmsSite extensionFolderSite = mainSite.createAlternativeSiteRootSite();
706            if (extensionFolderSite != null) {
707                sitesToProcess.add(extensionFolderSite);
708            }
709            for (CmsSite site : sitesToProcess) {
710                String folder = CmsFileUtil.addTrailingSeparator(site.getSiteRoot());
711                if (!siteroots.contains(folder)) {
712                    siteroots.add(folder);
713                    siteServers.put(folder, site.getSiteMatcher());
714                }
715            }
716        }
717        // add default site
718        if (workplaceMode && (m_defaultSite != null)) {
719            String folder = CmsFileUtil.addTrailingSeparator(m_defaultSite.getSiteRoot());
720            if (!siteroots.contains(folder)) {
721                siteroots.add(folder);
722            }
723        }
724
725        String storedSiteRoot = cms.getRequestContext().getSiteRoot();
726        try {
727            // for all operations here we need no context
728            cms.getRequestContext().setSiteRoot("/");
729            if (workplaceMode && OpenCms.getRoleManager().hasRole(cms, CmsRole.VFS_MANAGER)) {
730                if (!siteroots.contains("/")) {
731                    // add the root site if the user is in the workplace and has the required role
732                    siteroots.add("/");
733                }
734                if (!siteroots.contains(CmsFileUtil.addTrailingSeparator(storedSiteRoot))) {
735                    siteroots.add(CmsFileUtil.addTrailingSeparator(storedSiteRoot));
736                }
737            }
738            // add the shared site
739            String shared = OpenCms.getSiteManager().getSharedFolder();
740            if (showShared && (shared != null) && !siteroots.contains(shared)) {
741                siteroots.add(shared);
742            }
743            // all sites are compatible for root admins in the root OU, skip unnecessary tests
744            boolean allCompatible = OpenCms.getRoleManager().hasRole(cms, CmsRole.ROOT_ADMIN)
745                && (ouFqn.isEmpty() || ouFqn.equals(CmsOrganizationalUnit.SEPARATOR));
746            List<CmsResource> resources = Collections.emptyList();
747            if (!allCompatible) {
748                try {
749                    resources = OpenCms.getOrgUnitManager().getResourcesForOrganizationalUnit(cms, ouFqn);
750                } catch (CmsException e) {
751                    return Collections.emptyList();
752                }
753            }
754            Collections.sort(siteroots); // sort by resource name
755            Iterator<String> roots = siteroots.iterator();
756            while (roots.hasNext()) {
757                String folder = roots.next();
758                boolean compatible = allCompatible;
759                if (!compatible) {
760                    Iterator<CmsResource> itResources = resources.iterator();
761                    while (itResources.hasNext()) {
762                        CmsResource resource = itResources.next();
763                        if (resource.getRootPath().startsWith(folder) || folder.startsWith(resource.getRootPath())) {
764                            compatible = true;
765                            break;
766                        }
767                    }
768                }
769                // select only sites compatibles to the given organizational unit
770                if (compatible) {
771                    try {
772                        CmsResource res = cms.readResource(folder);
773                        if (!workplaceMode
774                            || cms.hasPermissions(
775                                res,
776                                CmsPermissionSet.ACCESS_VIEW,
777                                false,
778                                CmsResourceFilter.ONLY_VISIBLE)) {
779
780                            // get the title and the position from the system configuration first
781                            CmsSite configuredSite = getSiteForSiteRoot(CmsFileUtil.removeTrailingSeparator(folder));
782                            // CmsSite configuredSite = m_siteRootSites.get(CmsFileUtil.removeTrailingSeparator(folder));
783
784                            // get the title
785                            String title = null;
786                            if ((configuredSite != null)
787                                && CmsStringUtil.isNotEmptyOrWhitespaceOnly(configuredSite.getTitle())) {
788                                title = configuredSite.getTitle();
789                            }
790                            if (title == null) {
791                                title = getSiteTitle(cms, res);
792                            }
793
794                            // get the position
795                            String position = null;
796                            if ((configuredSite != null) && (configuredSite.getPosition() != Float.MAX_VALUE)) {
797                                position = Float.toString(configuredSite.getPosition());
798                            }
799                            if (position == null) {
800                                // not found, use the 'NavPos' property
801                                position = cms.readPropertyObject(
802                                    res,
803                                    CmsPropertyDefinition.PROPERTY_NAVPOS,
804                                    false).getValue();
805                            }
806                            if (configuredSite != null) {
807                                float pos = Float.MAX_VALUE;
808                                try {
809                                    pos = Float.parseFloat(position);
810                                } catch (Throwable e) {
811                                    // m_position will have Float.MAX_VALUE, so this site will appear last
812                                }
813                                CmsSite clone = configuredSite.clone();
814                                clone.setPosition(pos);
815                                clone.setTitle(title);
816                                if (filterMode == null) {
817                                    result.add(clone);
818                                } else {
819                                    if (filterMode.equals(clone.getSSLMode())) {
820                                        result.add(clone);
821                                    }
822                                }
823                            } else {
824                                // add the site to the result
825
826                                result.add(
827                                    new CmsSite(
828                                        folder,
829                                        res.getStructureId(),
830                                        title,
831                                        siteServers.get(folder),
832                                        position));
833                            }
834                        }
835                    } catch (CmsException e) {
836                        // user probably has no read access to the folder, ignore and continue iterating
837                    }
838                }
839            }
840
841            // sort and ensure that the shared folder is the last element in the list
842            Collections.sort(result, new Comparator<CmsSite>() {
843
844                public int compare(CmsSite o1, CmsSite o2) {
845
846                    if (isSharedFolder(o1.getSiteRoot())) {
847                        return +1;
848                    }
849                    if (isSharedFolder(o2.getSiteRoot())) {
850                        return -1;
851                    }
852                    return o1.compareTo(o2);
853                }
854            });
855        } catch (Throwable t) {
856            LOG.error(Messages.get().getBundle().key(Messages.LOG_READ_SITE_PROP_FAILED_0), t);
857        } finally {
858            // restore the user's current context
859            cms.getRequestContext().setSiteRoot(storedSiteRoot);
860        }
861        return result;
862
863    }
864
865    /**
866     * Returns a list of all sites available (visible) for the current user.<p>
867     *
868     * @param cms the current OpenCms user context
869     * @param workplaceMode if true, the root and current site is included for the admin user
870     *                      and the view permission is required to see the site root
871     * @param filterMode The CmsSLLMode to filter, null if no filter
872     *
873     * @return a list of all sites available for the current user
874     */
875    public List<CmsSite> getAvailableSites(CmsObject cms, boolean workplaceMode, CmsSSLMode filterMode) {
876
877        return getAvailableSites(cms, workplaceMode, workplaceMode, cms.getRequestContext().getOuFqn(), filterMode);
878    }
879
880    /**
881     * Returns a list of all {@link CmsSite} instances that are compatible to the given organizational unit.<p>
882     *
883     * @param cms the current OpenCms user context
884     * @param workplaceMode if true, the root and current site is included for the admin user
885     *                      and the view permission is required to see the site root
886     * @param ouFqn the organizational unit
887     *
888     * @return a list of all site available for the current user
889     */
890    public List<CmsSite> getAvailableSites(CmsObject cms, boolean workplaceMode, String ouFqn) {
891
892        return getAvailableSites(cms, workplaceMode, workplaceMode, ouFqn);
893    }
894
895    /**
896     * Returns the current site for the provided OpenCms user context object.<p>
897     *
898     * In the unlikely case that no site matches with the provided OpenCms user context,
899     * the default site is returned.<p>
900     *
901     * @param cms the OpenCms user context object to check for the site
902     *
903     * @return the current site for the provided OpenCms user context object
904     */
905    public CmsSite getCurrentSite(CmsObject cms) {
906
907        CmsSite site = getSiteForSiteRoot(cms.getRequestContext().getSiteRoot());
908        return (site == null) ? m_defaultSite : site;
909    }
910
911    /**
912     * Returns the default site.<p>
913     *
914     * @return the default site
915     */
916    public CmsSite getDefaultSite() {
917
918        return m_defaultSite;
919    }
920
921    /**
922     * Returns the defaultUri.<p>
923     *
924     * @return the defaultUri
925     */
926    public String getDefaultUri() {
927
928        return m_defaultUri;
929    }
930
931    /**
932     * Returns the shared folder path.<p>
933     *
934     * @return the shared folder path
935     */
936    public String getSharedFolder() {
937
938        return m_sharedFolder;
939    }
940
941    /**
942     * Returns the site for the given resource path, using the fall back site root
943     * in case the resource path is no root path.<p>
944     *
945     * In case neither the given resource path, nor the given fall back site root
946     * matches any configured site, the default site is returned.<p>
947     *
948     * Usually the fall back site root should be taken from {@link org.opencms.file.CmsRequestContext#getSiteRoot()},
949     * in which case a site for the site root should always exist.<p>
950     *
951     * This is the same as first calling {@link #getSiteForRootPath(String)} with the
952     * <code>resourcePath</code> parameter, and if this fails calling
953     * {@link #getSiteForSiteRoot(String)} with the <code>fallbackSiteRoot</code> parameter,
954     * and if this fails calling {@link #getDefaultSite()}.<p>
955     *
956     * @param rootPath the resource root path to get the site for
957     * @param fallbackSiteRoot site root to use in case the resource path is no root path
958     *
959     * @return the site for the given resource path, using the fall back site root
960     *      in case the resource path is no root path
961     *
962     * @see #getSiteForRootPath(String)
963     */
964    public CmsSite getSite(String rootPath, String fallbackSiteRoot) {
965
966        CmsSite result = getSiteForRootPath(rootPath);
967        if (result == null) {
968            result = getSiteForSiteRoot(fallbackSiteRoot);
969            if (result == null) {
970                result = getDefaultSite();
971            }
972        }
973        return result;
974    }
975
976    /**
977     * Gets the site which is mapped to the default uri, or the 'absent' value of no such site exists.<p>
978     *
979     * @return the optional site mapped to the default uri
980     */
981    public Optional<CmsSite> getSiteForDefaultUri() {
982
983        String defaultUri = getDefaultUri();
984        CmsSite candidate = m_siteRootSites.get(CmsFileUtil.removeTrailingSeparator(defaultUri));
985        return Optional.fromNullable(candidate);
986    }
987
988    /**
989     * Returns the site for the given resources root path,
990     * or <code>null</code> if the resources root path does not match any site.<p>
991     *
992     * @param rootPath the root path of a resource
993     *
994     * @return the site for the given resources root path,
995     *      or <code>null</code> if the resources root path does not match any site
996     *
997     * @see #getSiteForSiteRoot(String)
998     * @see #getSiteRoot(String)
999     */
1000    public CmsSite getSiteForRootPath(String rootPath) {
1001
1002        if ((rootPath.length() > 0) && !rootPath.endsWith("/")) {
1003            rootPath = rootPath + "/";
1004        }
1005        // most sites will be below the "/sites/" folder,
1006        CmsSite result = lookupSitesFolder(rootPath);
1007        if (result != null) {
1008            return result;
1009        }
1010        // look through all folders that are not below "/sites/"
1011        String siteRoot = lookupAdditionalSite(rootPath);
1012        if (siteRoot != null) {
1013            return getSiteForSiteRoot(siteRoot);
1014        }
1015        return m_alternativeSiteData.getSiteForRootPath(rootPath);
1016
1017    }
1018
1019    /**
1020     * Returns the site with has the provided site root,
1021     * or <code>null</code> if no configured site has that site root.<p>
1022     *
1023     * The site root must have the form:
1024     * <code>/sites/default</code>.<br>
1025     * That means there must be a leading, but no trailing slash.<p>
1026     *
1027     * @param siteRoot the site root to look up the site for
1028     *
1029     * @return the site with has the provided site root,
1030     *      or <code>null</code> if no configured site has that site root
1031     *
1032     * @see #getSiteForRootPath(String)
1033     */
1034    public CmsSite getSiteForSiteRoot(String siteRoot) {
1035
1036        if (siteRoot == null) {
1037            return null;
1038        }
1039        CmsSite result = m_siteRootSites.get(siteRoot);
1040        if (result != null) {
1041            return result;
1042        } else {
1043            return m_alternativeSiteData.getSiteForSiteRoot(siteRoot);
1044        }
1045    }
1046
1047    /**
1048     * Returns the site root part for the given resources root path,
1049     * or <code>null</code> if the given resources root path does not match any site root.<p>
1050     *
1051     * The site root returned will have the form:
1052     * <code>/sites/default</code>.<br>
1053     * That means there will a leading, but no trailing slash.<p>
1054     *
1055     * @param rootPath the root path of a resource
1056     *
1057     * @return the site root part of the resources root path,
1058     *      or <code>null</code> if the path does not match any site root
1059     *
1060     * @see #getSiteForRootPath(String)
1061     */
1062    public String getSiteRoot(String rootPath) {
1063
1064        // add a trailing slash, because the path may be the path of a site root itself
1065        if (!rootPath.endsWith("/")) {
1066            rootPath = rootPath + "/";
1067        }
1068        // most sites will be below the "/sites/" folder,
1069        CmsSite site = lookupSitesFolder(rootPath);
1070        if (site != null) {
1071            return site.getSiteRoot();
1072        }
1073        // look through all folders that are not below "/sites/"
1074        String result = lookupAdditionalSite(rootPath);
1075        if (result != null) {
1076            return result;
1077        }
1078        CmsSite extSite = m_alternativeSiteData.getSiteForRootPath(rootPath);
1079        if (extSite != null) {
1080            result = extSite.getSiteRoot();
1081        }
1082        return result;
1083
1084    }
1085
1086    /**
1087     * Returns an unmodifiable set of all configured site roots (Strings).<p>
1088     *
1089     * @return an unmodifiable set of all configured site roots (Strings)
1090     */
1091    public Set<String> getSiteRoots() {
1092
1093        return Sets.union(m_siteRootSites.keySet(), m_alternativeSiteData.getSiteRoots());
1094
1095    }
1096
1097    /**
1098     * Returns the map of configured sites, using
1099     * {@link CmsSiteMatcher} objects as keys and {@link CmsSite} objects as values.<p>
1100     *
1101     * @return the map of configured sites, using {@link CmsSiteMatcher}
1102     *      objects as keys and {@link CmsSite} objects as values
1103     */
1104    public Map<CmsSiteMatcher, CmsSite> getSites() {
1105
1106        return m_siteMatcherSites;
1107    }
1108
1109    /**
1110     * Returns the site title.<p>
1111     *
1112     * @param cms the cms context
1113     * @param resource the site root resource
1114     *
1115     * @return the title
1116     *
1117     * @throws CmsException in case reading the title property fails
1118     */
1119    public String getSiteTitle(CmsObject cms, CmsResource resource) throws CmsException {
1120
1121        String title = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue();
1122        if (title == null) {
1123            title = resource.getRootPath();
1124        }
1125        if (resource.getRootPath().equals(getSharedFolder())) {
1126            title = SHARED_FOLDER_TITLE;
1127        }
1128        return title;
1129    }
1130
1131    /**
1132     * Gets the SSLMode for given workplace server.<p>
1133     *
1134     * @param server to obtain ssl mode for
1135     * @return CmsSSLMode
1136     */
1137    public CmsSSLMode getSSLModeForWorkplaceServer(String server) {
1138
1139        if (server == null) {
1140            return CmsSSLMode.NO;
1141        }
1142        if (!m_workplaceServers.containsKey(server)) {
1143            return CmsSSLMode.NO;
1144        }
1145
1146        return m_workplaceServers.get(server);
1147    }
1148
1149    /**
1150     * Get web server scripting configurations.<p>
1151     *
1152     * @return Map with configuration data
1153     */
1154    public Map<String, String> getWebServerConfig() {
1155
1156        return m_apacheConfig;
1157    }
1158
1159    /**
1160     * Returns the workplace server.<p>
1161     *
1162     * @return the workplace server
1163     */
1164    public String getWorkplaceServer() {
1165
1166        return m_workplaceServers.keySet().isEmpty() ? null : m_workplaceServers.keySet().iterator().next();
1167    }
1168
1169    /**
1170     * Gets the first configured workplace server that matches the host from the current CmsRequestContext, or
1171     * the first configured workplace server if there is no match.
1172     *
1173     * <p>If there are no workplace configured at all, null is returned.
1174     *
1175     * @param cms the CmsObject used to check the host
1176     * @return the workplace server
1177     */
1178    public String getWorkplaceServer(CmsObject cms) {
1179
1180        if (m_workplaceServers.keySet().isEmpty()) {
1181            return null;
1182        }
1183        CmsSiteMatcher requestMatcher = cms.getRequestContext().getRequestMatcher();
1184        if (requestMatcher != null) {
1185            String reqHost = getHost(requestMatcher.toString());
1186            if (reqHost != null) {
1187                for (String wpServer : m_workplaceServers.keySet()) {
1188                    String wpHost = getHost(wpServer);
1189                    if (reqHost.equals(wpHost)) {
1190                        return wpServer;
1191                    }
1192                }
1193            }
1194        }
1195        return m_workplaceServers.keySet().iterator().next();
1196    }
1197
1198    /**
1199     * Returns the configured worklace servers.<p>
1200     *
1201     * @return the workplace servers
1202     */
1203    public List<String> getWorkplaceServers() {
1204
1205        return Collections.unmodifiableList(new ArrayList<String>(m_workplaceServers.keySet()));
1206    }
1207
1208    /**
1209     * Returns the configured worklace servers.<p>
1210     *
1211     * @param filterMode CmsSSLMode to filter results for.
1212     * @return the workplace servers
1213     */
1214    public List<String> getWorkplaceServers(CmsSSLMode filterMode) {
1215
1216        if (filterMode == null) {
1217            return getWorkplaceServers();
1218        }
1219        List<String> ret = new ArrayList<String>();
1220        for (String server : m_workplaceServers.keySet()) {
1221            if (m_workplaceServers.get(server).equals(filterMode)) {
1222                ret.add(server);
1223            }
1224        }
1225        return ret;
1226    }
1227
1228    /**
1229     * Returns the configured worklace servers.<p>
1230     *
1231     * @return the workplace servers
1232     */
1233    public Map<String, CmsSSLMode> getWorkplaceServersMap() {
1234
1235        return Collections.unmodifiableMap(m_workplaceServers);
1236    }
1237
1238    /**
1239     * Returns the site matcher that matches the workplace site.<p>
1240     *
1241     * @return the site matcher that matches the workplace site
1242     */
1243    public CmsSiteMatcher getWorkplaceSiteMatcher() {
1244
1245        return m_workplaceMatchers.isEmpty() ? null : m_workplaceMatchers.get(0);
1246    }
1247
1248    /**
1249     * Initializes the site manager with the OpenCms system configuration.<p>
1250     *
1251     * @param cms an OpenCms context object that must have been initialized with "Admin" permissions
1252     */
1253    public void initialize(CmsObject cms) {
1254
1255        if (CmsLog.INIT.isInfoEnabled()) {
1256            CmsLog.INIT.info(
1257                Messages.get().getBundle().key(
1258                    Messages.INIT_NUM_SITE_ROOTS_CONFIGURED_1,
1259                    Integer.valueOf((m_siteMatcherSites.size() + ((m_defaultUri != null) ? 1 : 0)))));
1260        }
1261
1262        try {
1263
1264            m_clone = OpenCms.initCmsObject(cms);
1265            m_clone.getRequestContext().setSiteRoot("");
1266            m_clone.getRequestContext().setCurrentProject(m_clone.readProject(CmsProject.ONLINE_PROJECT_NAME));
1267
1268            CmsObject cms_offline = OpenCms.initCmsObject(m_clone);
1269            CmsProject tempProject = null;
1270            try {
1271                tempProject = cms_offline.createProject(
1272                    "tempProjectSites",
1273                    "",
1274                    "/Users",
1275                    "/Users",
1276                    CmsProject.PROJECT_TYPE_TEMPORARY);
1277                cms_offline.getRequestContext().setCurrentProject(tempProject);
1278
1279            } catch (Exception e) {
1280                LOG.warn(e.getLocalizedMessage(), e);
1281            }
1282
1283            m_siteUUIDs = new HashMap<CmsUUID, CmsSite>();
1284            // check the presence of sites in VFS
1285
1286            m_onlyOfflineSites = new ArrayList<CmsSite>();
1287
1288            for (CmsSite site : m_siteMatcherSites.values()) {
1289                checkUUIDOfSiteRoot(site, m_clone, tempProject != null ? cms_offline : null);
1290                try {
1291                    CmsResource siteRes = m_clone.readResource(site.getSiteRoot());
1292                    site.setSiteRootUUID(siteRes.getStructureId());
1293
1294                    m_siteUUIDs.put(siteRes.getStructureId(), site);
1295                    // during server startup the digester can not access properties, so set the title afterwards
1296                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(site.getTitle())) {
1297                        String title = m_clone.readPropertyObject(
1298                            siteRes,
1299                            CmsPropertyDefinition.PROPERTY_TITLE,
1300                            false).getValue();
1301                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(title)) {
1302                            site.setTitle(title);
1303                        }
1304                    }
1305                } catch (Throwable t) {
1306                    if (CmsLog.INIT.isWarnEnabled()) {
1307                        CmsLog.INIT.warn(Messages.get().getBundle().key(Messages.INIT_NO_ROOT_FOLDER_1, site));
1308                    }
1309                }
1310            }
1311            if (tempProject != null) {
1312                cms_offline.deleteProject(tempProject.getUuid());
1313            }
1314
1315            // check the presence of the default site in VFS
1316            if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_defaultUri)) {
1317                m_defaultSite = null;
1318            } else {
1319                m_defaultSite = new CmsSite(m_defaultUri, CmsSiteMatcher.DEFAULT_MATCHER);
1320                try {
1321                    m_clone.readResource(m_defaultSite.getSiteRoot());
1322                } catch (Throwable t) {
1323                    if (CmsLog.INIT.isWarnEnabled()) {
1324                        CmsLog.INIT.warn(
1325                            Messages.get().getBundle().key(Messages.INIT_NO_ROOT_FOLDER_DEFAULT_SITE_1, m_defaultSite));
1326                    }
1327                }
1328            }
1329            if (m_defaultSite == null) {
1330                m_defaultSite = new CmsSite("/", CmsSiteMatcher.DEFAULT_MATCHER);
1331            }
1332            if (CmsLog.INIT.isInfoEnabled()) {
1333                if (m_defaultSite != null) {
1334                    CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DEFAULT_SITE_ROOT_1, m_defaultSite));
1335                } else {
1336                    CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DEFAULT_SITE_ROOT_0));
1337                }
1338            }
1339            initWorkplaceMatchers();
1340
1341            // set site lists to unmodifiable
1342            setSiteMatcherSites(m_siteMatcherSites);
1343
1344            // store additional site roots to optimize lookups later
1345            for (String root : m_siteRootSites.keySet()) {
1346                if (!root.startsWith(SITES_FOLDER) || (root.split("/").length >= 4)) {
1347                    m_additionalSiteRoots.add(root);
1348                }
1349            }
1350
1351            initExtensionSites();
1352
1353            if (m_sharedFolder == null) {
1354                m_sharedFolder = DEFAULT_SHARED_FOLDER;
1355            }
1356
1357            // initialization is done, set the frozen flag to true
1358            m_frozen = true;
1359        } catch (CmsException e) {
1360            LOG.warn(e.getLocalizedMessage(), e);
1361        }
1362        if (!m_isListenerSet) {
1363            OpenCms.addCmsEventListener(this, new int[] {I_CmsEventListener.EVENT_PUBLISH_PROJECT});
1364            m_isListenerSet = true;
1365        }
1366    }
1367
1368    /**
1369     * Checks if web server scripting is enabled.<p>
1370     *
1371     * @return true if web server scripting is set to available
1372     */
1373    public boolean isConfigurableWebServer() {
1374
1375        return m_apacheConfig != null;
1376    }
1377
1378    /**
1379     * Returns <code>true</code> if the given site matcher matches any configured site,
1380     * which includes the workplace site.<p>
1381     *
1382     * @param matcher the site matcher to match the site with
1383     *
1384     * @return <code>true</code> if the matcher matches a site
1385     */
1386    public boolean isMatching(CmsSiteMatcher matcher) {
1387
1388        boolean result = m_siteMatcherSites.get(matcher) != null;
1389        if (!result) {
1390            // try to match the workplace site
1391            result = isWorkplaceRequest(matcher);
1392        }
1393        return result;
1394    }
1395
1396    /**
1397     * Returns <code>true</code> if the given site matcher matches the current site.<p>
1398     *
1399     * @param cms the current OpenCms user context
1400     * @param matcher the site matcher to match the site with
1401     *
1402     * @return <code>true</code> if the matcher matches the current site
1403     */
1404    public boolean isMatchingCurrentSite(CmsObject cms, CmsSiteMatcher matcher) {
1405
1406        return m_siteMatcherSites.get(matcher) == getCurrentSite(cms);
1407    }
1408
1409    /**
1410     * Checks if old style secure server is allowed.<p>
1411     *
1412     * @return boolean
1413     */
1414    public boolean isOldStyleSecureServerAllowed() {
1415
1416        return m_oldStyleSecureServer;
1417    }
1418
1419    /**
1420     * Indicates if given site is only available for offline repository.<p>
1421     *
1422     * @param site to be looked up
1423     * @return true if only offline exists, false otherwise
1424     */
1425    public boolean isOnlyOfflineSite(CmsSite site) {
1426
1427        return m_onlyOfflineSites.contains(site);
1428    }
1429
1430    /**
1431     * Checks if the given path is that of a shared folder.<p>
1432     *
1433     * @param name a path prefix
1434     *
1435     * @return true if the given prefix represents a shared folder
1436     */
1437    public boolean isSharedFolder(String name) {
1438
1439        return (m_sharedFolder != null) && m_sharedFolder.equals(CmsStringUtil.joinPaths("/", name, "/"));
1440    }
1441
1442    /**
1443     * Checks whether a given root path is a site root.<p>
1444     *
1445     * @param rootPath a root path
1446     *
1447     * @return true if the given path is the path of a site root
1448     */
1449    public boolean isSiteRoot(String rootPath) {
1450
1451        String siteRoot = getSiteRoot(rootPath);
1452        rootPath = CmsStringUtil.joinPaths(rootPath, "/");
1453        return rootPath.equals(siteRoot);
1454
1455    }
1456
1457    /**
1458     * Checks if a given site is under another site.<p>
1459     *
1460     * @param site CmsSite to check
1461     * @return true if given site is invalid
1462     */
1463    public boolean isSiteUnderSite(CmsSite site) {
1464
1465        return isSiteUnderSite(site.getSiteRoot());
1466    }
1467
1468    /**
1469     * Checks if a given site is under another site.<p>
1470     *
1471     * @param siteRootPath site root path to check
1472     * @return true if given site is invalid
1473     */
1474    public boolean isSiteUnderSite(String siteRootPath) {
1475
1476        for (String siteRoot : getSiteRoots()) {
1477            if ((siteRootPath.length() > siteRoot.length())
1478                & siteRootPath.startsWith(CmsFileUtil.addTrailingSeparator(siteRoot))) {
1479                return true;
1480            }
1481        }
1482        return false;
1483    }
1484
1485    /**
1486     * Returns <code>true</code> if the given site matcher matches the configured OpenCms workplace.<p>
1487     *
1488     * @param matcher the site matcher to match the site with
1489     *
1490     * @return <code>true</code> if the given site matcher matches the configured OpenCms workplace
1491     */
1492    public boolean isWorkplaceRequest(CmsSiteMatcher matcher) {
1493
1494        return m_workplaceMatchers.contains(matcher);
1495    }
1496
1497    /**
1498     * Returns <code>true</code> if the given request is against the configured OpenCms workplace.<p>
1499     *
1500     * @param req the request to match
1501     *
1502     * @return <code>true</code> if the given request is against the configured OpenCms workplace
1503     */
1504    public boolean isWorkplaceRequest(HttpServletRequest req) {
1505
1506        if (req == null) {
1507            // this may be true inside a static export test case scenario
1508            return false;
1509        }
1510        return isWorkplaceRequest(getRequestMatcher(req));
1511    }
1512
1513    /**
1514     * Matches the given request against all configures sites and returns
1515     * the matching site, or the default site if no sites matches.<p>
1516     *
1517     * @param req the request to match
1518     *
1519     * @return the matching site, or the default site if no sites matches
1520     */
1521    public CmsSite matchRequest(HttpServletRequest req) {
1522
1523        CmsSiteMatcher matcher = getRequestMatcher(req);
1524        if (matcher.getTimeOffset() != 0) {
1525            HttpSession session = req.getSession();
1526            if (session != null) {
1527                session.setAttribute(
1528                    CmsContextInfo.ATTRIBUTE_REQUEST_TIME,
1529                    Long.valueOf(System.currentTimeMillis() + matcher.getTimeOffset()));
1530            }
1531        }
1532        CmsSite site = matchSite(matcher);
1533        if (site.matchAlternativeSiteRoot(OpenCmsCore.getPathInfo(req))) {
1534            CmsSite alternativeSite = site.createAlternativeSiteRootSite();
1535            if (alternativeSite != null) {
1536                LOG.debug(
1537                    req.getRequestURL().toString()
1538                        + ": "
1539                        + "Matched extension folder rule, changing site root from "
1540                        + site.getSiteRoot()
1541                        + " to "
1542                        + alternativeSite.getSiteRoot());
1543                site = alternativeSite;
1544            }
1545        }
1546
1547        if (LOG.isDebugEnabled()) {
1548            String requestServer = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
1549            LOG.debug(
1550                Messages.get().getBundle().key(
1551                    Messages.LOG_MATCHING_REQUEST_TO_SITE_2,
1552                    requestServer,
1553                    site.toString()));
1554        }
1555        return site;
1556    }
1557
1558    /**
1559     * Return the configured site that matches the given site matcher,
1560     * or the default site if no sites matches.<p>
1561     *
1562     * Does NOT match auto-generated sites from alternative site root mappings, since the site matcher does not contain path information.
1563     *
1564     * @param matcher the site matcher to match the site with
1565     * @return the matching site, or the default site if no sites matches
1566     */
1567    public CmsSite matchSite(CmsSiteMatcher matcher) {
1568
1569        CmsSite site = m_siteMatcherSites.get(matcher);
1570        if (site == null) {
1571            // return the default site (might be null as well)
1572            site = m_defaultSite;
1573        }
1574        return site;
1575    }
1576
1577    /**
1578     * Removes a site from the list of configured sites.<p>
1579     *
1580     * @param cms the cms object
1581     * @param site the site to remove
1582     *
1583     * @throws CmsException if something goes wrong
1584     */
1585    public void removeSite(CmsObject cms, CmsSite site) throws CmsException {
1586
1587        // check permissions
1588        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
1589            // simple unit tests will have runlevel 1 and no CmsObject
1590            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
1591        }
1592
1593        // un-freeze
1594        m_frozen = false;
1595
1596        // create a new map containing all existing sites without the one to remove
1597        Map<CmsSiteMatcher, CmsSite> siteMatcherSites = new HashMap<CmsSiteMatcher, CmsSite>();
1598        List<CmsSiteMatcher> matchersForSite = site.getAllMatchers();
1599        for (Map.Entry<CmsSiteMatcher, CmsSite> entry : m_siteMatcherSites.entrySet()) {
1600            if (!(matchersForSite.contains(entry.getKey()))) {
1601                // entry not the site itself nor an alias of the site nor the secure URL of the site, so add it
1602                siteMatcherSites.put(entry.getKey(), entry.getValue());
1603            }
1604        }
1605        setSiteMatcherSites(siteMatcherSites);
1606
1607        // remove the site from the map holding the site roots as keys and the sites as values
1608        Map<String, CmsSite> siteRootSites = new HashMap<String, CmsSite>(m_siteRootSites);
1609        siteRootSites.remove(site.getSiteRoot());
1610        m_siteRootSites = Collections.unmodifiableMap(siteRootSites);
1611
1612        // re-initialize, will freeze the state when finished
1613        initialize(cms);
1614        OpenCms.writeConfiguration(CmsSitesConfiguration.class);
1615    }
1616
1617    /**
1618     * Sets the default URI, this is only allowed during configuration.<p>
1619     *
1620     * If this method is called after the configuration is finished,
1621     * a <code>RuntimeException</code> is thrown.<p>
1622     *
1623     * @param defaultUri the defaultUri to set
1624     */
1625    public void setDefaultUri(String defaultUri) {
1626
1627        if (m_frozen) {
1628            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
1629        }
1630        m_defaultUri = defaultUri;
1631    }
1632
1633    /**
1634     * Sets the old style secure server boolean.<p>
1635     *
1636     * @param value value
1637     */
1638    public void setOldStyleSecureServerAllowed(String value) {
1639
1640        m_oldStyleSecureServer = Boolean.parseBoolean(StringUtil.toLowerCase(value));
1641    }
1642
1643    /**
1644     * Sets the shared folder path.<p>
1645     *
1646     * @param sharedFolder the shared folder path
1647     */
1648    public void setSharedFolder(String sharedFolder) {
1649
1650        if (m_frozen) {
1651            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
1652        }
1653        m_sharedFolder = CmsStringUtil.joinPaths("/", sharedFolder, "/");
1654    }
1655
1656    /**
1657     * Set webserver script configuration.<p>
1658     *
1659     *
1660     * @param webserverscript path
1661     * @param targetpath path
1662     * @param configtemplate path
1663     * @param securetemplate path
1664     * @param filenameprefix to add to files
1665     * @param loggingdir path
1666     */
1667    public void setWebServerScripting(
1668        String webserverscript,
1669        String targetpath,
1670        String configtemplate,
1671        String securetemplate,
1672        String filenameprefix,
1673        String loggingdir) {
1674
1675        m_apacheConfig = new HashMap<String, String>();
1676        m_apacheConfig.put(WEB_SERVER_CONFIG_WEBSERVERSCRIPT, webserverscript);
1677        m_apacheConfig.put(WEB_SERVER_CONFIG_TARGETPATH, targetpath);
1678        m_apacheConfig.put(WEB_SERVER_CONFIG_CONFIGTEMPLATE, configtemplate);
1679        m_apacheConfig.put(WEB_SERVER_CONFIG_SECURETEMPLATE, securetemplate);
1680        m_apacheConfig.put(WEB_SERVER_CONFIG_FILENAMEPREFIX, filenameprefix);
1681        m_apacheConfig.put(WEB_SERVER_CONFIG_LOGGINGDIR, loggingdir);
1682    }
1683
1684    /**
1685     * Returns true if the path starts with the shared folder path.<p>
1686     *
1687     * @param path the path to check
1688     *
1689     * @return true if the path starts with the shared folder path
1690     */
1691    public boolean startsWithShared(String path) {
1692
1693        return (m_sharedFolder != null) && CmsFileUtil.addTrailingSeparator(path).startsWith(m_sharedFolder);
1694    }
1695
1696    /**
1697     * Method for backward compability reasons. Not sure if really needed //TODO check!
1698     * CmsSSLMode are set to No as default.<p>
1699     *
1700     * @param cms the cms to use
1701     * @param defaultUri the default URI
1702     * @param workplaceServersList the workplace server URLs
1703     * @param sharedFolder the shared folder URI
1704     *
1705     * @throws CmsException if something goes wrong
1706     */
1707    public void updateGeneralSettings(
1708        CmsObject cms,
1709        String defaultUri,
1710        List<String> workplaceServersList,
1711        String sharedFolder)
1712    throws CmsException {
1713
1714        Map<String, CmsSSLMode> workplaceServers = new LinkedHashMap<String, CmsSSLMode>();
1715        for (String server : workplaceServersList) {
1716            if (m_workplaceServers.containsKey(server)) {
1717                workplaceServers.put(server, m_workplaceServers.get(server));
1718            } else {
1719                workplaceServers.put(server, CmsSSLMode.NO);
1720            }
1721        }
1722        updateGeneralSettings(cms, defaultUri, workplaceServers, sharedFolder);
1723    }
1724
1725    /**
1726     * Updates the general settings.<p>
1727     *
1728     * @param cms the cms to use
1729     * @param defaulrUri the default URI
1730     * @param workplaceServers the workplace server URLs
1731     * @param sharedFolder the shared folder URI
1732     *
1733     * @throws CmsException if something goes wrong
1734     */
1735    public void updateGeneralSettings(
1736        CmsObject cms,
1737        String defaulrUri,
1738        Map<String, CmsSSLMode> workplaceServers,
1739        String sharedFolder)
1740    throws CmsException {
1741
1742        CmsObject clone = OpenCms.initCmsObject(cms);
1743        clone.getRequestContext().setSiteRoot("");
1744
1745        // set the shared folder
1746        if ((sharedFolder == null)
1747            || sharedFolder.equals("")
1748            || sharedFolder.equals("/")
1749            || !sharedFolder.startsWith("/")
1750            || !sharedFolder.endsWith("/")
1751            || sharedFolder.startsWith("/sites/")) {
1752            throw new CmsException(
1753                Messages.get().container(Messages.ERR_INVALID_PATH_FOR_SHARED_FOLDER_1, sharedFolder));
1754        }
1755
1756        m_frozen = false;
1757        setDefaultUri(clone.readResource(defaulrUri).getRootPath());
1758        setSharedFolder(clone.readResource(sharedFolder).getRootPath());
1759        m_workplaceServers = workplaceServers;
1760        initialize(cms);
1761        m_frozen = true;
1762    }
1763
1764    /**
1765     * Updates or creates a site.<p>
1766     *
1767     * @param cms the CMS object
1768     * @param oldSite the site to remove if not <code>null</code>
1769     * @param newSite the site to add if not <code>null</code>
1770     *
1771     * @throws CmsException if something goes wrong
1772     */
1773    public void updateSite(CmsObject cms, CmsSite oldSite, CmsSite newSite) throws CmsException {
1774
1775        if (oldSite != null) {
1776            // remove the old site
1777            removeSite(cms, oldSite);
1778        }
1779
1780        if (newSite != null) {
1781            // add the new site
1782            addSite(cms, newSite);
1783        }
1784    }
1785
1786    /**
1787     * Returns true if this request goes to a secure site.<p>
1788     *
1789     * @param req the request to check
1790     *
1791     * @return true if the request goes to a secure site
1792     */
1793    public boolean usesSecureSite(HttpServletRequest req) {
1794
1795        CmsSite site = matchRequest(req);
1796        if (site == null) {
1797            return false;
1798        }
1799        CmsSiteMatcher secureMatcher = site.getSecureServerMatcher();
1800        boolean result = false;
1801        if (secureMatcher != null) {
1802            result = secureMatcher.equals(getRequestMatcher(req));
1803        }
1804        return result;
1805    }
1806
1807    /**
1808     * Validates the site root, throwing an exception if the validation fails.
1809     *
1810     * @param siteRoot the site root to check
1811     */
1812    public void validateSiteRoot(String siteRoot) {
1813
1814        if (!isValidSiteRoot(siteRoot)) {
1815            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_INVALID_SITE_ROOT_1, siteRoot));
1816        }
1817    }
1818
1819    /**
1820     * Adds a new Site matcher object to the map of server names.
1821     *
1822     * @param matcher the SiteMatcher of the server
1823     * @param site the site to add
1824     */
1825    private void addServer(CmsSiteMatcher matcher, CmsSite site) {
1826
1827        Map<CmsSiteMatcher, CmsSite> siteMatcherSites = new HashMap<CmsSiteMatcher, CmsSite>(m_siteMatcherSites);
1828        siteMatcherSites.put(matcher, site);
1829        setSiteMatcherSites(siteMatcherSites);
1830    }
1831
1832    /**
1833     * Fetches UUID for given site root from online and offline repository.<p>
1834     *
1835     * @param site to read and set UUID for
1836     * @param clone online CmsObject
1837     * @param cms_offline offline CmsObject
1838     */
1839    private void checkUUIDOfSiteRoot(CmsSite site, CmsObject clone, CmsObject cms_offline) {
1840
1841        CmsUUID id = null;
1842        try {
1843            id = clone.readResource(site.getSiteRoot()).getStructureId();
1844        } catch (CmsException e) {
1845            //Ok, site root not available for online repository.
1846        }
1847
1848        if ((id == null) && (cms_offline != null)) {
1849            try {
1850                id = cms_offline.readResource(site.getSiteRoot()).getStructureId();
1851                m_onlyOfflineSites.add(site);
1852            } catch (CmsException e) {
1853                //Siteroot not valid for on- and offline repository.
1854            }
1855        }
1856        if (id != null) {
1857            site.setSiteRootUUID(id);
1858            LOG.debug("Initializing site id: " + site + " => " + id);
1859            m_siteUUIDs.put(id, site);
1860        }
1861    }
1862
1863    /**
1864     * Gets an offline project to read offline resources from.<p>
1865     *
1866     * @return CmsProject
1867     */
1868    private CmsProject getOfflineProject() {
1869
1870        try {
1871            return m_clone.readProject("Offline");
1872        } catch (CmsException e) {
1873            try {
1874                for (CmsProject p : OpenCms.getOrgUnitManager().getAllAccessibleProjects(m_clone, "/", true)) {
1875                    if (!p.isOnlineProject()) {
1876                        return p;
1877                    }
1878                }
1879            } catch (CmsException e1) {
1880                LOG.error("Unable to get ptoject", e);
1881            }
1882        }
1883        return null;
1884    }
1885
1886    /**
1887     * Returns the site matcher for the given request.<p>
1888     *
1889     * @param req the request to get the site matcher for
1890     *
1891     * @return the site matcher for the given request
1892     */
1893    private CmsSiteMatcher getRequestMatcher(HttpServletRequest req) {
1894
1895        CmsSiteMatcher matcher = new CmsSiteMatcher(req.getScheme(), req.getServerName(), req.getServerPort());
1896        // this is required to get the right configured time offset
1897        int index = m_siteMatchers.indexOf(matcher);
1898        if (index < 0) {
1899            return matcher;
1900        }
1901        return m_siteMatchers.get(index);
1902    }
1903
1904    /**
1905     * Finds the configured extension folders for all normal sites and stores them in a separate list.
1906     */
1907    private void initExtensionSites() {
1908
1909        m_alternativeSiteData = new AlternativeSiteData(m_siteMatcherSites.values());
1910    }
1911
1912    /**
1913     * Initializes the workplace matchers.<p>
1914     */
1915    private void initWorkplaceMatchers() {
1916
1917        List<CmsSiteMatcher> matchers = new ArrayList<CmsSiteMatcher>();
1918        if (!m_workplaceServers.isEmpty()) {
1919            Map<String, CmsSiteMatcher> matchersByUrl = Maps.newHashMap();
1920            for (String server : m_workplaceServers.keySet()) {
1921                CmsSSLMode mode = m_workplaceServers.get(server);
1922                CmsSiteMatcher matcher = new CmsSiteMatcher(server);
1923                if ((mode == CmsSSLMode.LETS_ENCRYPT) || (mode == CmsSSLMode.MANUAL_EP_TERMINATION)) {
1924                    CmsSiteMatcher httpMatcher = matcher.forDifferentScheme("http");
1925                    CmsSiteMatcher httpsMatcher = matcher.forDifferentScheme("https");
1926                    for (CmsSiteMatcher current : new CmsSiteMatcher[] {httpMatcher, httpsMatcher}) {
1927                        matchersByUrl.put(current.getUrl(), current);
1928                        if (CmsLog.INIT.isInfoEnabled()) {
1929                            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_1, matcher));
1930                        }
1931                    }
1932                } else {
1933                    matchersByUrl.put(matcher.getUrl(), matcher);
1934                    if (CmsLog.INIT.isInfoEnabled()) {
1935                        CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_1, matcher));
1936                    }
1937
1938                }
1939            }
1940            matchers = Lists.newArrayList(matchersByUrl.values());
1941        } else if (CmsLog.INIT.isInfoEnabled()) {
1942            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_0));
1943        }
1944        m_workplaceMatchers = matchers;
1945    }
1946
1947    /**
1948     * Checks whether the given matcher is included in the currently configured and valid matchers.<p>
1949     *
1950     * @param matcher the matcher to check
1951     *
1952     * @return <code>true</code> in case the given matcher is included in the currently configured and valid matchers
1953     */
1954    private boolean isServerValid(CmsSiteMatcher matcher) {
1955
1956        return !m_siteMatcherSites.containsKey(matcher);
1957
1958    }
1959
1960    /**
1961     * Validates the site root.
1962     *
1963     * @param uri the site root to validate
1964     * @return true if the site root is valid
1965     */
1966    private boolean isValidSiteRoot(String uri) {
1967
1968        if ("".equals(uri)
1969            || "/".equals(uri)
1970            || CmsSiteManagerImpl.SITES_FOLDER.equals(uri)
1971            || CmsSiteManagerImpl.SITES_FOLDER.equals(uri + "/")) {
1972            return false;
1973        }
1974        return true;
1975    }
1976
1977    /**
1978     * Returns <code>true</code> if the given root path matches any of the stored additional sites.<p>
1979     *
1980     * @param rootPath the root path to check
1981     *
1982     * @return <code>true</code> if the given root path matches any of the stored additional sites
1983     */
1984    private String lookupAdditionalSite(String rootPath) {
1985
1986        for (int i = 0, size = m_additionalSiteRoots.size(); i < size; i++) {
1987            String siteRoot = m_additionalSiteRoots.get(i);
1988            if (rootPath.startsWith(siteRoot + "/")) {
1989                return siteRoot;
1990            }
1991        }
1992        return null;
1993    }
1994
1995    /**
1996     * Returns the configured site if the given root path matches site in the "/sites/" folder,
1997     * or <code>null</code> otherwise.<p>
1998     *
1999     * @param rootPath the root path to check
2000     *
2001     * @return the configured site if the given root path matches site in the "/sites/" folder,
2002     *      or <code>null</code> otherwise
2003     */
2004    private CmsSite lookupSitesFolder(String rootPath) {
2005
2006        int pos = rootPath.indexOf('/', SITES_FOLDER_POS);
2007        if (pos > 0) {
2008            // this assumes that the root path may likely start with something like "/sites/default/"
2009            // just cut the first 2 directories from the root path and do a direct lookup in the internal map
2010            return m_siteRootSites.get(rootPath.substring(0, pos));
2011        }
2012        return null;
2013    }
2014
2015    /**
2016     * Sets the class member variables {@link #m_siteMatcherSites} and  {@link #m_siteMatchers}
2017     * from the provided map of configured site matchers.<p>
2018     *
2019     * @param siteMatcherSites the site matches to set
2020     */
2021    private void setSiteMatcherSites(Map<CmsSiteMatcher, CmsSite> siteMatcherSites) {
2022
2023        m_siteMatcherSites = Collections.unmodifiableMap(siteMatcherSites);
2024        m_siteMatchers = Collections.unmodifiableList(new ArrayList<CmsSiteMatcher>(m_siteMatcherSites.keySet()));
2025    }
2026}