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                    new Integer((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 = cms_offline.createProject(
1270                "tempProjectSites",
1271                "",
1272                "/Users",
1273                "/Users",
1274                CmsProject.PROJECT_TYPE_TEMPORARY);
1275            cms_offline.getRequestContext().setCurrentProject(tempProject);
1276
1277            m_siteUUIDs = new HashMap<CmsUUID, CmsSite>();
1278            // check the presence of sites in VFS
1279
1280            m_onlyOfflineSites = new ArrayList<CmsSite>();
1281
1282            for (CmsSite site : m_siteMatcherSites.values()) {
1283                checkUUIDOfSiteRoot(site, m_clone, cms_offline);
1284                try {
1285                    CmsResource siteRes = m_clone.readResource(site.getSiteRoot());
1286                    site.setSiteRootUUID(siteRes.getStructureId());
1287
1288                    m_siteUUIDs.put(siteRes.getStructureId(), site);
1289                    // during server startup the digester can not access properties, so set the title afterwards
1290                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(site.getTitle())) {
1291                        String title = m_clone.readPropertyObject(
1292                            siteRes,
1293                            CmsPropertyDefinition.PROPERTY_TITLE,
1294                            false).getValue();
1295                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(title)) {
1296                            site.setTitle(title);
1297                        }
1298                    }
1299                } catch (Throwable t) {
1300                    if (CmsLog.INIT.isWarnEnabled()) {
1301                        CmsLog.INIT.warn(Messages.get().getBundle().key(Messages.INIT_NO_ROOT_FOLDER_1, site));
1302                    }
1303                }
1304            }
1305            cms_offline.deleteProject(tempProject.getUuid());
1306
1307            // check the presence of the default site in VFS
1308            if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_defaultUri)) {
1309                m_defaultSite = null;
1310            } else {
1311                m_defaultSite = new CmsSite(m_defaultUri, CmsSiteMatcher.DEFAULT_MATCHER);
1312                try {
1313                    m_clone.readResource(m_defaultSite.getSiteRoot());
1314                } catch (Throwable t) {
1315                    if (CmsLog.INIT.isWarnEnabled()) {
1316                        CmsLog.INIT.warn(
1317                            Messages.get().getBundle().key(Messages.INIT_NO_ROOT_FOLDER_DEFAULT_SITE_1, m_defaultSite));
1318                    }
1319                }
1320            }
1321            if (m_defaultSite == null) {
1322                m_defaultSite = new CmsSite("/", CmsSiteMatcher.DEFAULT_MATCHER);
1323            }
1324            if (CmsLog.INIT.isInfoEnabled()) {
1325                if (m_defaultSite != null) {
1326                    CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DEFAULT_SITE_ROOT_1, m_defaultSite));
1327                } else {
1328                    CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DEFAULT_SITE_ROOT_0));
1329                }
1330            }
1331            initWorkplaceMatchers();
1332
1333            // set site lists to unmodifiable
1334            setSiteMatcherSites(m_siteMatcherSites);
1335
1336            // store additional site roots to optimize lookups later
1337            for (String root : m_siteRootSites.keySet()) {
1338                if (!root.startsWith(SITES_FOLDER) || (root.split("/").length >= 4)) {
1339                    m_additionalSiteRoots.add(root);
1340                }
1341            }
1342
1343            initExtensionSites();
1344
1345            if (m_sharedFolder == null) {
1346                m_sharedFolder = DEFAULT_SHARED_FOLDER;
1347            }
1348
1349            // initialization is done, set the frozen flag to true
1350            m_frozen = true;
1351        } catch (CmsException e) {
1352            LOG.warn(e.getLocalizedMessage(), e);
1353        }
1354        if (!m_isListenerSet) {
1355            OpenCms.addCmsEventListener(this, new int[] {I_CmsEventListener.EVENT_PUBLISH_PROJECT});
1356            m_isListenerSet = true;
1357        }
1358    }
1359
1360    /**
1361     * Checks if web server scripting is enabled.<p>
1362     *
1363     * @return true if web server scripting is set to available
1364     */
1365    public boolean isConfigurableWebServer() {
1366
1367        return m_apacheConfig != null;
1368    }
1369
1370    /**
1371     * Returns <code>true</code> if the given site matcher matches any configured site,
1372     * which includes the workplace site.<p>
1373     *
1374     * @param matcher the site matcher to match the site with
1375     *
1376     * @return <code>true</code> if the matcher matches a site
1377     */
1378    public boolean isMatching(CmsSiteMatcher matcher) {
1379
1380        boolean result = m_siteMatcherSites.get(matcher) != null;
1381        if (!result) {
1382            // try to match the workplace site
1383            result = isWorkplaceRequest(matcher);
1384        }
1385        return result;
1386    }
1387
1388    /**
1389     * Returns <code>true</code> if the given site matcher matches the current site.<p>
1390     *
1391     * @param cms the current OpenCms user context
1392     * @param matcher the site matcher to match the site with
1393     *
1394     * @return <code>true</code> if the matcher matches the current site
1395     */
1396    public boolean isMatchingCurrentSite(CmsObject cms, CmsSiteMatcher matcher) {
1397
1398        return m_siteMatcherSites.get(matcher) == getCurrentSite(cms);
1399    }
1400
1401    /**
1402     * Checks if old style secure server is allowed.<p>
1403     *
1404     * @return boolean
1405     */
1406    public boolean isOldStyleSecureServerAllowed() {
1407
1408        return m_oldStyleSecureServer;
1409    }
1410
1411    /**
1412     * Indicates if given site is only available for offline repository.<p>
1413     *
1414     * @param site to be looked up
1415     * @return true if only offline exists, false otherwise
1416     */
1417    public boolean isOnlyOfflineSite(CmsSite site) {
1418
1419        return m_onlyOfflineSites.contains(site);
1420    }
1421
1422    /**
1423     * Checks if the given path is that of a shared folder.<p>
1424     *
1425     * @param name a path prefix
1426     *
1427     * @return true if the given prefix represents a shared folder
1428     */
1429    public boolean isSharedFolder(String name) {
1430
1431        return (m_sharedFolder != null) && m_sharedFolder.equals(CmsStringUtil.joinPaths("/", name, "/"));
1432    }
1433
1434    /**
1435     * Checks whether a given root path is a site root.<p>
1436     *
1437     * @param rootPath a root path
1438     *
1439     * @return true if the given path is the path of a site root
1440     */
1441    public boolean isSiteRoot(String rootPath) {
1442
1443        String siteRoot = getSiteRoot(rootPath);
1444        rootPath = CmsStringUtil.joinPaths(rootPath, "/");
1445        return rootPath.equals(siteRoot);
1446
1447    }
1448
1449    /**
1450     * Checks if a given site is under another site.<p>
1451     *
1452     * @param site CmsSite to check
1453     * @return true if given site is invalid
1454     */
1455    public boolean isSiteUnderSite(CmsSite site) {
1456
1457        return isSiteUnderSite(site.getSiteRoot());
1458    }
1459
1460    /**
1461     * Checks if a given site is under another site.<p>
1462     *
1463     * @param siteRootPath site root path to check
1464     * @return true if given site is invalid
1465     */
1466    public boolean isSiteUnderSite(String siteRootPath) {
1467
1468        for (String siteRoot : getSiteRoots()) {
1469            if ((siteRootPath.length() > siteRoot.length())
1470                & siteRootPath.startsWith(CmsFileUtil.addTrailingSeparator(siteRoot))) {
1471                return true;
1472            }
1473        }
1474        return false;
1475    }
1476
1477    /**
1478     * Returns <code>true</code> if the given site matcher matches the configured OpenCms workplace.<p>
1479     *
1480     * @param matcher the site matcher to match the site with
1481     *
1482     * @return <code>true</code> if the given site matcher matches the configured OpenCms workplace
1483     */
1484    public boolean isWorkplaceRequest(CmsSiteMatcher matcher) {
1485
1486        return m_workplaceMatchers.contains(matcher);
1487    }
1488
1489    /**
1490     * Returns <code>true</code> if the given request is against the configured OpenCms workplace.<p>
1491     *
1492     * @param req the request to match
1493     *
1494     * @return <code>true</code> if the given request is against the configured OpenCms workplace
1495     */
1496    public boolean isWorkplaceRequest(HttpServletRequest req) {
1497
1498        if (req == null) {
1499            // this may be true inside a static export test case scenario
1500            return false;
1501        }
1502        return isWorkplaceRequest(getRequestMatcher(req));
1503    }
1504
1505    /**
1506     * Matches the given request against all configures sites and returns
1507     * the matching site, or the default site if no sites matches.<p>
1508     *
1509     * @param req the request to match
1510     *
1511     * @return the matching site, or the default site if no sites matches
1512     */
1513    public CmsSite matchRequest(HttpServletRequest req) {
1514
1515        CmsSiteMatcher matcher = getRequestMatcher(req);
1516        if (matcher.getTimeOffset() != 0) {
1517            HttpSession session = req.getSession();
1518            if (session != null) {
1519                session.setAttribute(
1520                    CmsContextInfo.ATTRIBUTE_REQUEST_TIME,
1521                    new Long(System.currentTimeMillis() + matcher.getTimeOffset()));
1522            }
1523        }
1524        CmsSite site = matchSite(matcher);
1525        if (site.matchAlternativeSiteRoot(OpenCmsCore.getPathInfo(req))) {
1526            CmsSite alternativeSite = site.createAlternativeSiteRootSite();
1527            if (alternativeSite != null) {
1528                LOG.debug(
1529                    req.getRequestURL().toString()
1530                        + ": "
1531                        + "Matched extension folder rule, changing site root from "
1532                        + site.getSiteRoot()
1533                        + " to "
1534                        + alternativeSite.getSiteRoot());
1535                site = alternativeSite;
1536            }
1537        }
1538
1539        if (LOG.isDebugEnabled()) {
1540            String requestServer = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
1541            LOG.debug(
1542                Messages.get().getBundle().key(
1543                    Messages.LOG_MATCHING_REQUEST_TO_SITE_2,
1544                    requestServer,
1545                    site.toString()));
1546        }
1547        return site;
1548    }
1549
1550    /**
1551     * Return the configured site that matches the given site matcher,
1552     * or the default site if no sites matches.<p>
1553     *
1554     * Does NOT match auto-generated sites from alternative site root mappings, since the site matcher does not contain path information.
1555     *
1556     * @param matcher the site matcher to match the site with
1557     * @return the matching site, or the default site if no sites matches
1558     */
1559    public CmsSite matchSite(CmsSiteMatcher matcher) {
1560
1561        CmsSite site = m_siteMatcherSites.get(matcher);
1562        if (site == null) {
1563            // return the default site (might be null as well)
1564            site = m_defaultSite;
1565        }
1566        return site;
1567    }
1568
1569    /**
1570     * Removes a site from the list of configured sites.<p>
1571     *
1572     * @param cms the cms object
1573     * @param site the site to remove
1574     *
1575     * @throws CmsException if something goes wrong
1576     */
1577    public void removeSite(CmsObject cms, CmsSite site) throws CmsException {
1578
1579        // check permissions
1580        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
1581            // simple unit tests will have runlevel 1 and no CmsObject
1582            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
1583        }
1584
1585        // un-freeze
1586        m_frozen = false;
1587
1588        // create a new map containing all existing sites without the one to remove
1589        Map<CmsSiteMatcher, CmsSite> siteMatcherSites = new HashMap<CmsSiteMatcher, CmsSite>();
1590        List<CmsSiteMatcher> matchersForSite = site.getAllMatchers();
1591        for (Map.Entry<CmsSiteMatcher, CmsSite> entry : m_siteMatcherSites.entrySet()) {
1592            if (!(matchersForSite.contains(entry.getKey()))) {
1593                // entry not the site itself nor an alias of the site nor the secure URL of the site, so add it
1594                siteMatcherSites.put(entry.getKey(), entry.getValue());
1595            }
1596        }
1597        setSiteMatcherSites(siteMatcherSites);
1598
1599        // remove the site from the map holding the site roots as keys and the sites as values
1600        Map<String, CmsSite> siteRootSites = new HashMap<String, CmsSite>(m_siteRootSites);
1601        siteRootSites.remove(site.getSiteRoot());
1602        m_siteRootSites = Collections.unmodifiableMap(siteRootSites);
1603
1604        // re-initialize, will freeze the state when finished
1605        initialize(cms);
1606        OpenCms.writeConfiguration(CmsSitesConfiguration.class);
1607    }
1608
1609    /**
1610     * Sets the default URI, this is only allowed during configuration.<p>
1611     *
1612     * If this method is called after the configuration is finished,
1613     * a <code>RuntimeException</code> is thrown.<p>
1614     *
1615     * @param defaultUri the defaultUri to set
1616     */
1617    public void setDefaultUri(String defaultUri) {
1618
1619        if (m_frozen) {
1620            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
1621        }
1622        m_defaultUri = defaultUri;
1623    }
1624
1625    /**
1626     * Sets the old style secure server boolean.<p>
1627     *
1628     * @param value value
1629     */
1630    public void setOldStyleSecureServerAllowed(String value) {
1631
1632        m_oldStyleSecureServer = Boolean.parseBoolean(StringUtil.toLowerCase(value));
1633    }
1634
1635    /**
1636     * Sets the shared folder path.<p>
1637     *
1638     * @param sharedFolder the shared folder path
1639     */
1640    public void setSharedFolder(String sharedFolder) {
1641
1642        if (m_frozen) {
1643            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_CONFIG_FROZEN_0));
1644        }
1645        m_sharedFolder = CmsStringUtil.joinPaths("/", sharedFolder, "/");
1646    }
1647
1648    /**
1649     * Set webserver script configuration.<p>
1650     *
1651     *
1652     * @param webserverscript path
1653     * @param targetpath path
1654     * @param configtemplate path
1655     * @param securetemplate path
1656     * @param filenameprefix to add to files
1657     * @param loggingdir path
1658     */
1659    public void setWebServerScripting(
1660        String webserverscript,
1661        String targetpath,
1662        String configtemplate,
1663        String securetemplate,
1664        String filenameprefix,
1665        String loggingdir) {
1666
1667        m_apacheConfig = new HashMap<String, String>();
1668        m_apacheConfig.put(WEB_SERVER_CONFIG_WEBSERVERSCRIPT, webserverscript);
1669        m_apacheConfig.put(WEB_SERVER_CONFIG_TARGETPATH, targetpath);
1670        m_apacheConfig.put(WEB_SERVER_CONFIG_CONFIGTEMPLATE, configtemplate);
1671        m_apacheConfig.put(WEB_SERVER_CONFIG_SECURETEMPLATE, securetemplate);
1672        m_apacheConfig.put(WEB_SERVER_CONFIG_FILENAMEPREFIX, filenameprefix);
1673        m_apacheConfig.put(WEB_SERVER_CONFIG_LOGGINGDIR, loggingdir);
1674    }
1675
1676    /**
1677     * Returns true if the path starts with the shared folder path.<p>
1678     *
1679     * @param path the path to check
1680     *
1681     * @return true if the path starts with the shared folder path
1682     */
1683    public boolean startsWithShared(String path) {
1684
1685        return (m_sharedFolder != null) && CmsFileUtil.addTrailingSeparator(path).startsWith(m_sharedFolder);
1686    }
1687
1688    /**
1689     * Method for backward compability reasons. Not sure if really needed //TODO check!
1690     * CmsSSLMode are set to No as default.<p>
1691     *
1692     * @param cms the cms to use
1693     * @param defaultUri the default URI
1694     * @param workplaceServersList the workplace server URLs
1695     * @param sharedFolder the shared folder URI
1696     *
1697     * @throws CmsException if something goes wrong
1698     */
1699    public void updateGeneralSettings(
1700        CmsObject cms,
1701        String defaultUri,
1702        List<String> workplaceServersList,
1703        String sharedFolder)
1704    throws CmsException {
1705
1706        Map<String, CmsSSLMode> workplaceServers = new LinkedHashMap<String, CmsSSLMode>();
1707        for (String server : workplaceServersList) {
1708            if (m_workplaceServers.containsKey(server)) {
1709                workplaceServers.put(server, m_workplaceServers.get(server));
1710            } else {
1711                workplaceServers.put(server, CmsSSLMode.NO);
1712            }
1713        }
1714        updateGeneralSettings(cms, defaultUri, workplaceServers, sharedFolder);
1715    }
1716
1717    /**
1718     * Updates the general settings.<p>
1719     *
1720     * @param cms the cms to use
1721     * @param defaulrUri the default URI
1722     * @param workplaceServers the workplace server URLs
1723     * @param sharedFolder the shared folder URI
1724     *
1725     * @throws CmsException if something goes wrong
1726     */
1727    public void updateGeneralSettings(
1728        CmsObject cms,
1729        String defaulrUri,
1730        Map<String, CmsSSLMode> workplaceServers,
1731        String sharedFolder)
1732    throws CmsException {
1733
1734        CmsObject clone = OpenCms.initCmsObject(cms);
1735        clone.getRequestContext().setSiteRoot("");
1736
1737        // set the shared folder
1738        if ((sharedFolder == null)
1739            || sharedFolder.equals("")
1740            || sharedFolder.equals("/")
1741            || !sharedFolder.startsWith("/")
1742            || !sharedFolder.endsWith("/")
1743            || sharedFolder.startsWith("/sites/")) {
1744            throw new CmsException(
1745                Messages.get().container(Messages.ERR_INVALID_PATH_FOR_SHARED_FOLDER_1, sharedFolder));
1746        }
1747
1748        m_frozen = false;
1749        setDefaultUri(clone.readResource(defaulrUri).getRootPath());
1750        setSharedFolder(clone.readResource(sharedFolder).getRootPath());
1751        m_workplaceServers = workplaceServers;
1752        initialize(cms);
1753        m_frozen = true;
1754    }
1755
1756    /**
1757     * Updates or creates a site.<p>
1758     *
1759     * @param cms the CMS object
1760     * @param oldSite the site to remove if not <code>null</code>
1761     * @param newSite the site to add if not <code>null</code>
1762     *
1763     * @throws CmsException if something goes wrong
1764     */
1765    public void updateSite(CmsObject cms, CmsSite oldSite, CmsSite newSite) throws CmsException {
1766
1767        if (oldSite != null) {
1768            // remove the old site
1769            removeSite(cms, oldSite);
1770        }
1771
1772        if (newSite != null) {
1773            // add the new site
1774            addSite(cms, newSite);
1775        }
1776    }
1777
1778    /**
1779     * Returns true if this request goes to a secure site.<p>
1780     *
1781     * @param req the request to check
1782     *
1783     * @return true if the request goes to a secure site
1784     */
1785    public boolean usesSecureSite(HttpServletRequest req) {
1786
1787        CmsSite site = matchRequest(req);
1788        if (site == null) {
1789            return false;
1790        }
1791        CmsSiteMatcher secureMatcher = site.getSecureServerMatcher();
1792        boolean result = false;
1793        if (secureMatcher != null) {
1794            result = secureMatcher.equals(getRequestMatcher(req));
1795        }
1796        return result;
1797    }
1798
1799    /**
1800     * Validates the site root, throwing an exception if the validation fails.
1801     *
1802     * @param siteRoot the site root to check
1803     */
1804    public void validateSiteRoot(String siteRoot) {
1805
1806        if (!isValidSiteRoot(siteRoot)) {
1807            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_INVALID_SITE_ROOT_1, siteRoot));
1808        }
1809    }
1810
1811    /**
1812     * Adds a new Site matcher object to the map of server names.
1813     *
1814     * @param matcher the SiteMatcher of the server
1815     * @param site the site to add
1816     */
1817    private void addServer(CmsSiteMatcher matcher, CmsSite site) {
1818
1819        Map<CmsSiteMatcher, CmsSite> siteMatcherSites = new HashMap<CmsSiteMatcher, CmsSite>(m_siteMatcherSites);
1820        siteMatcherSites.put(matcher, site);
1821        setSiteMatcherSites(siteMatcherSites);
1822    }
1823
1824    /**
1825     * Fetches UUID for given site root from online and offline repository.<p>
1826     *
1827     * @param site to read and set UUID for
1828     * @param clone online CmsObject
1829     * @param cms_offline offline CmsObject
1830     */
1831    private void checkUUIDOfSiteRoot(CmsSite site, CmsObject clone, CmsObject cms_offline) {
1832
1833        CmsUUID id = null;
1834        try {
1835            id = clone.readResource(site.getSiteRoot()).getStructureId();
1836        } catch (CmsException e) {
1837            //Ok, site root not available for online repository.
1838        }
1839
1840        if (id == null) {
1841            try {
1842                id = cms_offline.readResource(site.getSiteRoot()).getStructureId();
1843                m_onlyOfflineSites.add(site);
1844            } catch (CmsException e) {
1845                //Siteroot not valid for on- and offline repository.
1846            }
1847        }
1848        if (id != null) {
1849            site.setSiteRootUUID(id);
1850            m_siteUUIDs.put(id, site);
1851        }
1852    }
1853
1854    /**
1855     * Gets an offline project to read offline resources from.<p>
1856     *
1857     * @return CmsProject
1858     */
1859    private CmsProject getOfflineProject() {
1860
1861        try {
1862            return m_clone.readProject("Offline");
1863        } catch (CmsException e) {
1864            try {
1865                for (CmsProject p : OpenCms.getOrgUnitManager().getAllAccessibleProjects(m_clone, "/", true)) {
1866                    if (!p.isOnlineProject()) {
1867                        return p;
1868                    }
1869                }
1870            } catch (CmsException e1) {
1871                LOG.error("Unable to get ptoject", e);
1872            }
1873        }
1874        return null;
1875    }
1876
1877    /**
1878     * Returns the site matcher for the given request.<p>
1879     *
1880     * @param req the request to get the site matcher for
1881     *
1882     * @return the site matcher for the given request
1883     */
1884    private CmsSiteMatcher getRequestMatcher(HttpServletRequest req) {
1885
1886        CmsSiteMatcher matcher = new CmsSiteMatcher(req.getScheme(), req.getServerName(), req.getServerPort());
1887        // this is required to get the right configured time offset
1888        int index = m_siteMatchers.indexOf(matcher);
1889        if (index < 0) {
1890            return matcher;
1891        }
1892        return m_siteMatchers.get(index);
1893    }
1894
1895    /**
1896     * Finds the configured extension folders for all normal sites and stores them in a separate list.
1897     */
1898    private void initExtensionSites() {
1899
1900        m_alternativeSiteData = new AlternativeSiteData(m_siteMatcherSites.values());
1901    }
1902
1903    /**
1904     * Initializes the workplace matchers.<p>
1905     */
1906    private void initWorkplaceMatchers() {
1907
1908        List<CmsSiteMatcher> matchers = new ArrayList<CmsSiteMatcher>();
1909        if (!m_workplaceServers.isEmpty()) {
1910            Map<String, CmsSiteMatcher> matchersByUrl = Maps.newHashMap();
1911            for (String server : m_workplaceServers.keySet()) {
1912                CmsSSLMode mode = m_workplaceServers.get(server);
1913                CmsSiteMatcher matcher = new CmsSiteMatcher(server);
1914                if ((mode == CmsSSLMode.LETS_ENCRYPT) || (mode == CmsSSLMode.MANUAL_EP_TERMINATION)) {
1915                    CmsSiteMatcher httpMatcher = matcher.forDifferentScheme("http");
1916                    CmsSiteMatcher httpsMatcher = matcher.forDifferentScheme("https");
1917                    for (CmsSiteMatcher current : new CmsSiteMatcher[] {httpMatcher, httpsMatcher}) {
1918                        matchersByUrl.put(current.getUrl(), current);
1919                        if (CmsLog.INIT.isInfoEnabled()) {
1920                            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_1, matcher));
1921                        }
1922                    }
1923                } else {
1924                    matchersByUrl.put(matcher.getUrl(), matcher);
1925                    if (CmsLog.INIT.isInfoEnabled()) {
1926                        CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_1, matcher));
1927                    }
1928
1929                }
1930            }
1931            matchers = Lists.newArrayList(matchersByUrl.values());
1932        } else if (CmsLog.INIT.isInfoEnabled()) {
1933            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WORKPLACE_SITE_0));
1934        }
1935        m_workplaceMatchers = matchers;
1936    }
1937
1938    /**
1939     * Checks whether the given matcher is included in the currently configured and valid matchers.<p>
1940     *
1941     * @param matcher the matcher to check
1942     *
1943     * @return <code>true</code> in case the given matcher is included in the currently configured and valid matchers
1944     */
1945    private boolean isServerValid(CmsSiteMatcher matcher) {
1946
1947        return !m_siteMatcherSites.containsKey(matcher);
1948
1949    }
1950
1951    /**
1952     * Validates the site root.
1953     *
1954     * @param uri the site root to validate
1955     * @return true if the site root is valid
1956     */
1957    private boolean isValidSiteRoot(String uri) {
1958
1959        if ("".equals(uri) || "/".equals(uri)) {
1960            return false;
1961        }
1962        return true;
1963    }
1964
1965    /**
1966     * Returns <code>true</code> if the given root path matches any of the stored additional sites.<p>
1967     *
1968     * @param rootPath the root path to check
1969     *
1970     * @return <code>true</code> if the given root path matches any of the stored additional sites
1971     */
1972    private String lookupAdditionalSite(String rootPath) {
1973
1974        for (int i = 0, size = m_additionalSiteRoots.size(); i < size; i++) {
1975            String siteRoot = m_additionalSiteRoots.get(i);
1976            if (rootPath.startsWith(siteRoot + "/")) {
1977                return siteRoot;
1978            }
1979        }
1980        return null;
1981    }
1982
1983    /**
1984     * Returns the configured site if the given root path matches site in the "/sites/" folder,
1985     * or <code>null</code> otherwise.<p>
1986     *
1987     * @param rootPath the root path to check
1988     *
1989     * @return the configured site if the given root path matches site in the "/sites/" folder,
1990     *      or <code>null</code> otherwise
1991     */
1992    private CmsSite lookupSitesFolder(String rootPath) {
1993
1994        int pos = rootPath.indexOf('/', SITES_FOLDER_POS);
1995        if (pos > 0) {
1996            // this assumes that the root path may likely start with something like "/sites/default/"
1997            // just cut the first 2 directories from the root path and do a direct lookup in the internal map
1998            return m_siteRootSites.get(rootPath.substring(0, pos));
1999        }
2000        return null;
2001    }
2002
2003    /**
2004     * Sets the class member variables {@link #m_siteMatcherSites} and  {@link #m_siteMatchers}
2005     * from the provided map of configured site matchers.<p>
2006     *
2007     * @param siteMatcherSites the site matches to set
2008     */
2009    private void setSiteMatcherSites(Map<CmsSiteMatcher, CmsSite> siteMatcherSites) {
2010
2011        m_siteMatcherSites = Collections.unmodifiableMap(siteMatcherSites);
2012        m_siteMatchers = Collections.unmodifiableList(new ArrayList<CmsSiteMatcher>(m_siteMatcherSites.keySet()));
2013    }
2014}