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