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