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