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