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, 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.ui.components; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.file.CmsResourceFilter; 033import org.opencms.file.CmsVfsResourceNotFoundException; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.security.CmsPermissionViolationException; 038import org.opencms.site.CmsSite; 039import org.opencms.ui.CmsVaadinUtils; 040import org.opencms.ui.components.CmsExtendedSiteSelector.SiteSelectorOption; 041import org.opencms.util.CmsFileUtil; 042import org.opencms.util.CmsPath; 043import org.opencms.util.CmsStringUtil; 044import org.opencms.workplace.explorer.CmsResourceUtil; 045 046import java.util.ArrayList; 047import java.util.Collections; 048import java.util.LinkedHashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.Optional; 052 053import org.apache.commons.logging.Log; 054 055import com.google.common.base.Objects; 056import com.google.common.collect.ArrayListMultimap; 057import com.google.common.collect.Multimap; 058import com.vaadin.data.provider.ListDataProvider; 059import com.vaadin.ui.ComboBox; 060 061/** 062 * Site selector widget which also optionally offers subsite options. 063 **/ 064public class CmsExtendedSiteSelector extends ComboBox<SiteSelectorOption> { 065 066 /** 067 * Class representing a single option. 068 */ 069 public static class SiteSelectorOption { 070 071 /** The label for the option. */ 072 private String m_label; 073 074 /** The path in the site (may be null). */ 075 private String m_path; 076 077 /** The site root. */ 078 private String m_site; 079 080 /** 081 * Creates a new instance. 082 * 083 * @param site the site root 084 * @param path the path in the site (may be null) 085 * @param label the option label 086 */ 087 public SiteSelectorOption(String site, String path, String label) { 088 089 m_site = normalizePath(site); 090 m_path = normalizePath(path); 091 m_label = label; 092 } 093 094 /** 095 * @see java.lang.Object#equals(java.lang.Object) 096 */ 097 @Override 098 public boolean equals(Object obj) { 099 100 if (this == obj) { 101 return true; 102 } 103 if (obj == null) { 104 return false; 105 } 106 if (getClass() != obj.getClass()) { 107 return false; 108 } 109 SiteSelectorOption other = (SiteSelectorOption)obj; 110 return Objects.equal(m_site, other.m_site) && Objects.equal(m_path, other.m_path); 111 } 112 113 /** 114 * Gets the option label. 115 * 116 * @return the option label 117 */ 118 public String getLabel() { 119 120 return m_label; 121 } 122 123 /** 124 * Gets the path to jump to as a site path (may be null). 125 * 126 * @return the path to jump to 127 */ 128 public String getPath() { 129 130 return m_path; 131 } 132 133 /** 134 * Gets the site root 135 * 136 * @return the site root 137 */ 138 public String getSite() { 139 140 return m_site; 141 } 142 143 /** 144 * @see java.lang.Object#hashCode() 145 */ 146 @Override 147 public int hashCode() { 148 149 final int prime = 31; 150 int result = 1; 151 result = (prime * result) + ((m_path == null) ? 0 : m_path.hashCode()); 152 result = (prime * result) + ((m_site == null) ? 0 : m_site.hashCode()); 153 return result; 154 } 155 156 /** 157 * Cuts off trailing slashes if the path is not null. 158 * 159 * @param path a path 160 * @return the normalized path 161 */ 162 private String normalizePath(String path) { 163 164 if (path == null) { 165 return null; 166 } 167 return CmsFileUtil.removeTrailingSeparator(path); 168 } 169 170 } 171 172 /** Logger instance for this class. */ 173 private static final Log LOG = CmsLog.getLog(CmsExtendedSiteSelector.class); 174 175 /** Serial version id. */ 176 private static final long serialVersionUID = 1L; 177 178 /** Longer default page length for site selectors. */ 179 public static final int LONG_PAGE_LENGTH = 20; 180 181 /** True if the options have been initialized. */ 182 private boolean m_initialized; 183 184 /** The options. */ 185 private List<SiteSelectorOption> m_options = new ArrayList<>(); 186 187 /** 188 * Creates a new instance.<p> 189 * 190 * To actually use the widget, the initOptions method has to be called first. 191 */ 192 public CmsExtendedSiteSelector() { 193 194 super(); 195 196 setDataProvider(new ListDataProvider<SiteSelectorOption>(m_options)); 197 setItemCaptionGenerator(item -> item.getLabel()); 198 setTextInputAllowed(true); 199 setEmptySelectionAllowed(false); 200 addAttachListener(evt -> { 201 if (!m_initialized) { 202 throw new RuntimeException(getClass().getName() + " must be initialized with initOptions"); 203 } 204 205 }); 206 } 207 208 /** 209 * Builds a list of site selector option that also includes subsites with the 'include in site selector' option enabled in their configuration. 210 * 211 * @param cms the current CMS context 212 * @param addSubsites true if subsite options should be added 213 * @return the the extended site selecctor options 214 */ 215 public static List<CmsExtendedSiteSelector.SiteSelectorOption> getExplorerSiteSelectorOptions( 216 CmsObject cms, 217 boolean addSubsites) { 218 219 LinkedHashMap<String, String> siteOptions = CmsVaadinUtils.getAvailableSitesMap(cms); 220 List<String> subsites = new ArrayList<>( 221 OpenCms.getADEManager().getSubsitesForSiteSelector( 222 cms.getRequestContext().getCurrentProject().isOnlineProject())); 223 Collections.sort(subsites); 224 Multimap<CmsPath, CmsExtendedSiteSelector.SiteSelectorOption> subsitesForSite = ArrayListMultimap.create(); 225 if (addSubsites) { 226 try { 227 CmsObject titleCms = OpenCms.initCmsObject(cms); 228 titleCms.getRequestContext().setSiteRoot(""); 229 for (String subsite : subsites) { 230 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(subsite); 231 if ((site != null) && site.isSubsiteSelectionEnabled()) { // only use subsites that are in an actual site; also, subsite selection must be enabled on the site 232 CmsPath siteRootPath = new CmsPath(site.getSiteRoot()); 233 if (!siteRootPath.equals(new CmsPath(subsite))) { // Don't allow the site itself as a subsite 234 Optional<String> remainingPath = CmsStringUtil.removePrefixPath( 235 site.getSiteRoot(), 236 subsite); 237 if (remainingPath.isPresent()) { 238 try { 239 CmsResource subsiteRes = titleCms.readResource( 240 subsite, 241 CmsResourceFilter.ONLY_VISIBLE_NO_DELETED); 242 CmsResourceUtil resUtil = new CmsResourceUtil(titleCms, subsiteRes); 243 String title = resUtil.getTitle(); 244 if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { 245 title = subsiteRes.getName(); 246 } 247 SiteSelectorOption option = new SiteSelectorOption( 248 site.getSiteRoot(), 249 remainingPath.get(), 250 "\u2013 " + title); 251 subsitesForSite.put(siteRootPath, option); 252 } catch (CmsPermissionViolationException | CmsVfsResourceNotFoundException e) { 253 LOG.info(e.getLocalizedMessage(), e); 254 } catch (CmsException e) { 255 LOG.warn(e.getLocalizedMessage(), e); 256 } 257 } 258 } 259 } 260 } 261 } catch (Exception e) { 262 LOG.error(e.getLocalizedMessage(), e); 263 } 264 } 265 List<SiteSelectorOption> result = new ArrayList<>(); 266 for (Map.Entry<String, String> entry : siteOptions.entrySet()) { 267 result.add(new SiteSelectorOption(entry.getKey(), null, entry.getValue())); 268 result.addAll(subsitesForSite.get(new CmsPath(entry.getKey()))); 269 } 270 return result; 271 272 } 273 274 /** 275 * Gets the option for the specific site root (without any subsite path). 276 * 277 * <p>If no option for the site is found, returns null. 278 * 279 * @param siteRoot the site root 280 * @return the option for the site root 281 */ 282 public SiteSelectorOption getOptionForSiteRoot(String siteRoot) { 283 284 for (SiteSelectorOption option : m_options) { 285 if (Objects.equal(option.getSite(), siteRoot) && (option.getPath() == null)) { 286 return option; 287 } 288 } 289 return null; 290 291 } 292 293 /** 294 * Initializes the select options. 295 * 296 * @param cms the CMS context 297 * @param addSubsites true if subsites should be shown 298 */ 299 public void initOptions(CmsObject cms, boolean addSubsites) { 300 301 List<SiteSelectorOption> options = CmsExtendedSiteSelector.getExplorerSiteSelectorOptions(cms, addSubsites); 302 m_options.clear(); 303 m_options.addAll(options); 304 m_initialized = true; 305 } 306 307 /** 308 * Selects a specific site. 309 * 310 * @param siteRoot the site root 311 */ 312 public void selectSite(String siteRoot) { 313 314 SiteSelectorOption option = getOptionForSiteRoot(siteRoot); 315 if (option != null) { 316 setValue(option); 317 } 318 319 } 320 321}