001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.loader; 029 030import org.opencms.ade.containerpage.Messages; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.flex.CmsFlexController; 036import org.opencms.gwt.shared.CmsClientVariantInfo; 037import org.opencms.gwt.shared.CmsGwtConstants; 038import org.opencms.gwt.shared.CmsTemplateContextInfo; 039import org.opencms.main.CmsException; 040import org.opencms.main.CmsLog; 041import org.opencms.main.OpenCms; 042import org.opencms.util.CmsDefaultSet; 043import org.opencms.util.CmsRequestUtil; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.xml.content.CmsXmlContentProperty; 046 047import java.util.ArrayList; 048import java.util.Collections; 049import java.util.LinkedHashMap; 050import java.util.List; 051import java.util.Locale; 052import java.util.Map; 053import java.util.concurrent.ConcurrentHashMap; 054import java.util.concurrent.TimeUnit; 055 056import javax.servlet.http.HttpServletRequest; 057 058import org.apache.commons.logging.Log; 059 060/** 061 * Manager class for template context providers.<p> 062 */ 063public class CmsTemplateContextManager { 064 065 /** Request attribute used to set the template context during RPC calls. */ 066 public static final String ATTR_RPC_CONTEXT_OVERRIDE = "ATTR_RPC_CONTEXT_OVERRIDE"; 067 068 /** A bean containing information about the selected template. */ 069 public static final String ATTR_TEMPLATE_BEAN = "ATTR_TEMPLATE_BEAN"; 070 071 /** The request attribute in which the template context is stored. */ 072 public static final String ATTR_TEMPLATE_CONTEXT = "templateContext"; 073 074 /** Attribute name which contains the template name for non-dynamically selected templates. */ 075 public static final String ATTR_TEMPLATE_NAME = "cmsTemplateName"; 076 077 /** Attribute name for the template resource. */ 078 public static final String ATTR_TEMPLATE_RESOURCE = "cmsTemplateResource"; 079 080 /** The prefix used in the template property to activate dynamic template selection. */ 081 public static final String DYNAMIC_TEMPLATE_PREFIX = "provider="; 082 083 /** Legacy prefix for property providers. */ 084 private static final String DYNAMIC_TEMPLATE_LEGACY_PREFIX = "dynamic:"; 085 086 /** The logger instance for this class. */ 087 private static final Log LOG = CmsLog.getLog(CmsTemplateContextManager.class); 088 089 /** Cached allowed context map. */ 090 private volatile Map<String, CmsDefaultSet<String>> m_cachedContextMap = null; 091 092 /** The CMS context. */ 093 private CmsObject m_cms; 094 095 /** A cache in which the template context provider instances are stored, with their class name as the key. */ 096 private Map<String, I_CmsTemplateContextProvider> m_providerInstances = new ConcurrentHashMap<String, I_CmsTemplateContextProvider>(); 097 098 /** 099 * Creates a new instance.<p> 100 * 101 * @param cms the CMS context to use 102 */ 103 public CmsTemplateContextManager(CmsObject cms) { 104 105 m_cms = cms; 106 CmsFlexController.registerUncacheableAttribute(ATTR_TEMPLATE_RESOURCE); 107 CmsFlexController.registerUncacheableAttribute(ATTR_TEMPLATE_CONTEXT); 108 CmsFlexController.registerUncacheableAttribute(ATTR_TEMPLATE_RESOURCE); 109 OpenCms.getExecutor().scheduleWithFixedDelay(this::updateContextMap, 1, 15, TimeUnit.SECONDS); 110 } 111 112 /** 113 * Checks if the property value starts with the prefix which marks a dynamic template provider.<p> 114 * 115 * @param propertyValue the property value to check 116 * @return true if the value has the format of a dynamic template provider 117 */ 118 public static boolean hasPropertyPrefix(String propertyValue) { 119 120 return (propertyValue != null) 121 && (propertyValue.startsWith(DYNAMIC_TEMPLATE_PREFIX) 122 || propertyValue.startsWith(DYNAMIC_TEMPLATE_LEGACY_PREFIX)); 123 } 124 125 /** 126 * Checks if a template property value refers to a template context provider.<p> 127 * 128 * @param templatePath the template property value 129 * @return true if this value refers to a template context provider 130 */ 131 public static boolean isProvider(String templatePath) { 132 133 if (CmsStringUtil.isEmptyOrWhitespaceOnly(templatePath)) { 134 return false; 135 } 136 templatePath = templatePath.trim(); 137 return templatePath.startsWith(DYNAMIC_TEMPLATE_LEGACY_PREFIX) 138 || templatePath.startsWith(DYNAMIC_TEMPLATE_PREFIX); 139 } 140 141 /** 142 * Removes the prefix which marks a property value as a dynamic template provider.<p> 143 * 144 * @param propertyValue the value from which to remove the prefix 145 * 146 * @return the string with the prefix removed 147 */ 148 public static String removePropertyPrefix(String propertyValue) { 149 150 if (propertyValue == null) { 151 return null; 152 } 153 if (propertyValue.startsWith(DYNAMIC_TEMPLATE_PREFIX)) { 154 return propertyValue.substring(DYNAMIC_TEMPLATE_PREFIX.length()); 155 } 156 if (propertyValue.startsWith(DYNAMIC_TEMPLATE_LEGACY_PREFIX)) { 157 return propertyValue.substring(DYNAMIC_TEMPLATE_LEGACY_PREFIX.length()); 158 } 159 return propertyValue; 160 } 161 162 /** 163 * Creates a bean with information about the current template context, for use in the client-side code.<p> 164 * 165 * @param cms the current CMS context 166 * @param request the current request 167 * 168 * @return the bean with the template context information 169 */ 170 public CmsTemplateContextInfo getContextInfoBean(CmsObject cms, HttpServletRequest request) { 171 172 CmsTemplateContextInfo result = new CmsTemplateContextInfo(); 173 CmsTemplateContext context = (CmsTemplateContext)request.getAttribute(ATTR_TEMPLATE_CONTEXT); 174 if (context != null) { 175 result.setCurrentContext(context.getKey()); 176 177 I_CmsTemplateContextProvider provider = context.getProvider(); 178 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 179 result.setMenuLabel(provider.getMenuLabel(locale)); 180 result.setDefaultLabel(provider.getDefaultLabel(locale)); 181 result.setShouldShowElementTemplateContextSelection( 182 provider.shouldShowElementTemplateContextSelection(cms)); 183 CmsXmlContentProperty settingDefinition = createTemplateContextsPropertyDefinition(provider, locale); 184 result.setSettingDefinition(settingDefinition); 185 String cookieName = context.getProvider().getOverrideCookieName(); 186 if (cookieName != null) { 187 String cookieValue = CmsRequestUtil.getCookieValue(request.getCookies(), cookieName); 188 result.setSelectedContext(cookieValue); 189 } 190 result.setCookieName(cookieName); 191 Map<String, String> niceNames = new LinkedHashMap<String, String>(); 192 for (Map.Entry<String, CmsTemplateContext> entry : provider.getAllContexts().entrySet()) { 193 CmsTemplateContext otherContext = entry.getValue(); 194 if (provider.isHiddenContext(otherContext.getKey())) { 195 continue; 196 } 197 String niceName = otherContext.getLocalizedName(locale); 198 niceNames.put(otherContext.getKey(), niceName); 199 for (CmsClientVariant variant : otherContext.getClientVariants().values()) { 200 CmsClientVariantInfo info = new CmsClientVariantInfo( 201 variant.getName(), 202 variant.getNiceName(locale), 203 variant.getScreenWidth(), 204 variant.getScreenHeight(), 205 variant.getParameters()); 206 result.setClientVariant(otherContext.getKey(), variant.getName(), info); 207 } 208 } 209 result.setContextLabels(niceNames); 210 String providerKey = OpenCms.getTemplateContextManager().getProviderKey(provider); 211 result.setContextProvider(providerKey); 212 } 213 Map<String, CmsDefaultSet<String>> allowedContextMap = safeGetAllowedContextMap(); 214 result.setAllowedContexts(allowedContextMap); 215 return result; 216 } 217 218 /** 219 * Gets the key of a cached template provider (consisting of class name and parameters) 220 * that can later be used as an argument to getTemplateContextProvider. 221 * 222 * <p>If the provider is not already cached, returns null. 223 * 224 * @param provider the template provider 225 * 226 * @return the cache key 227 */ 228 public String getProviderKey(I_CmsTemplateContextProvider provider) { 229 230 // Just do a linear search over the map entries. There should only be a small number of different configured template providers, 231 // so this is not a problem for performance. 232 for (Map.Entry<String, I_CmsTemplateContextProvider> entry : m_providerInstances.entrySet()) { 233 if (entry.getValue() == provider) { 234 return entry.getKey(); 235 } 236 } 237 return null; 238 } 239 240 /** 241 * Gets the template context to use.<p> 242 * 243 * @param providerName the name of the template context provider 244 * @param cms the current CMS context 245 * @param request the current request 246 * @param resource the current resource 247 * 248 * @return the current template context 249 */ 250 public CmsTemplateContext getTemplateContext( 251 String providerName, 252 CmsObject cms, 253 HttpServletRequest request, 254 CmsResource resource) { 255 256 I_CmsTemplateContextProvider provider = getTemplateContextProvider(providerName); 257 if (provider == null) { 258 return null; 259 } 260 String cookieName = provider.getOverrideCookieName(); 261 String forcedValue = null; 262 if (request != null) { 263 String paramTemplateContext = request.getParameter(CmsGwtConstants.PARAM_TEMPLATE_CONTEXT); 264 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(paramTemplateContext)) { 265 forcedValue = paramTemplateContext; 266 } else if (cookieName != null) { 267 forcedValue = CmsRequestUtil.getCookieValue(request.getCookies(), cookieName); 268 } 269 } 270 if (forcedValue != null) { 271 Map<String, CmsTemplateContext> contextMap = provider.getAllContexts(); 272 if (contextMap.containsKey(forcedValue)) { 273 CmsTemplateContext contextBean = contextMap.get(forcedValue); 274 return new CmsTemplateContext( 275 contextBean.getKey(), 276 contextBean.getTemplatePath(), 277 contextBean.getMessageContainer(), 278 contextBean.getProvider(), 279 contextBean.getClientVariants().values(), 280 true); 281 282 } 283 } 284 return provider.getTemplateContext(cms, request, resource); 285 } 286 287 /** 288 * Gets the template context provider for a given path.<p> 289 * 290 * @param cms the current CMS context 291 * @param path the path for which the template context provider should be determined 292 * 293 * @return the template context provider for the given path 294 * 295 * @throws CmsException if something goes wrong 296 */ 297 public I_CmsTemplateContextProvider getTemplateContextProvider(CmsObject cms, String path) throws CmsException { 298 299 CmsResource resource = cms.readResource(path); 300 I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(resource); 301 if (loader instanceof A_CmsXmlDocumentLoader) { 302 String propertyName = ((A_CmsXmlDocumentLoader)loader).getTemplatePropertyDefinition(); 303 List<CmsProperty> properties = cms.readPropertyObjects(resource, true); 304 CmsProperty property = CmsProperty.get(propertyName, properties); 305 if ((property != null) && !property.isNullProperty()) { 306 String propertyValue = property.getValue(); 307 if (CmsTemplateContextManager.hasPropertyPrefix(propertyValue)) { 308 return getTemplateContextProvider(removePropertyPrefix(propertyValue)); 309 } 310 } 311 return null; 312 } else { 313 return null; 314 } 315 } 316 317 /** 318 * Retrieves an instance of a template context provider given its name (optionally prefixed by the 'dynamic:' prefix).<p> 319 * 320 * @param providerName the name of the provider 321 * 322 * @return an instance of the provider class 323 */ 324 public I_CmsTemplateContextProvider getTemplateContextProvider(String providerName) { 325 326 if (providerName == null) { 327 return null; 328 } 329 providerName = providerName.trim(); 330 providerName = removePropertyPrefix(providerName); 331 String providerClassName = providerName; 332 String providerConfig = ""; 333 334 // get provider configuration string if available 335 int separatorIndex = providerName.indexOf(","); 336 if (separatorIndex > 0) { 337 providerClassName = providerName.substring(0, separatorIndex); 338 providerConfig = providerName.substring(separatorIndex + 1); 339 } 340 341 I_CmsTemplateContextProvider result = m_providerInstances.get(providerName); 342 if (result == null) { 343 try { 344 Class<?> providerClass = Class.forName(providerClassName, false, getClass().getClassLoader()); 345 if (I_CmsTemplateContextProvider.class.isAssignableFrom(providerClass)) { 346 result = (I_CmsTemplateContextProvider)providerClass.newInstance(); 347 result.initialize(m_cms, providerConfig); 348 //note: we use the provider name as a key here, which includes configuration parameters 349 m_providerInstances.put(providerName, result); 350 } 351 } catch (Throwable t) { 352 LOG.error(t.getLocalizedMessage(), t); 353 } 354 } 355 return result; 356 } 357 358 /** 359 * Utility method which either reads a property from the template used for a specific resource, or from the template context provider used for the resource if available.<p> 360 * 361 * @param cms the CMS context to use 362 * @param res the resource from whose template or template context provider the property should be read 363 * @param propertyName the property name 364 * @param fallbackValue the fallback value 365 * 366 * @return the property value 367 */ 368 public String readPropertyFromTemplate(CmsObject cms, CmsResource res, String propertyName, String fallbackValue) { 369 370 try { 371 CmsProperty templateProp = cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_TEMPLATE, true); 372 String templatePath = templateProp.getValue().trim(); 373 if (hasPropertyPrefix(templatePath)) { 374 I_CmsTemplateContextProvider provider = getTemplateContextProvider(templatePath); 375 return provider.readCommonProperty(cms, propertyName, fallbackValue); 376 } else { 377 return cms.readPropertyObject(templatePath, propertyName, false).getValue(fallbackValue); 378 } 379 } catch (Exception e) { 380 LOG.error(e.getLocalizedMessage(), e); 381 return fallbackValue; 382 } 383 } 384 385 /** 386 * Helper method to check whether a given type should not be shown in a context.<p> 387 * 388 * @param contextKey the key of the template context 389 * @param typeName the type name 390 * 391 * @return true if the context does not prohibit showing the type 392 */ 393 public boolean shouldShowType(String contextKey, String typeName) { 394 395 Map<String, CmsDefaultSet<String>> allowedContextMap = safeGetAllowedContextMap(); 396 CmsDefaultSet<String> allowedContexts = allowedContextMap.get(typeName); 397 if (allowedContexts == null) { 398 return true; 399 } 400 return allowedContexts.contains(contextKey); 401 } 402 403 /** 404 * Creates the setting definition for the templateContexts setting.<p> 405 * 406 * @param contextProvider the context provider 407 * @param locale the current locale 408 * 409 * @return the setting definition 410 */ 411 protected CmsXmlContentProperty createTemplateContextsPropertyDefinition( 412 I_CmsTemplateContextProvider contextProvider, 413 Locale locale) { 414 415 if (contextProvider == null) { 416 return null; 417 } 418 List<String> contextOptions = new ArrayList<String>(); 419 for (CmsTemplateContext context : contextProvider.getAllContexts().values()) { 420 contextOptions.add(context.getKey() + ":" + context.getLocalizedName(locale)); 421 } 422 String widgetConfig = CmsStringUtil.listAsString(contextOptions, "|"); 423 424 String niceName = Messages.get().getBundle(locale).key(Messages.GUI_SETTING_TEMPLATE_CONTEXTS_NAME_0); 425 String description = Messages.get().getBundle(locale).key(Messages.GUI_SETTING_TEMPLATE_CONTEXTS_DESCRIPTION_0); 426 CmsXmlContentProperty propDef = new CmsXmlContentProperty( 427 CmsTemplateContextInfo.SETTING, 428 "string", 429 "multicheck", 430 widgetConfig, 431 null, 432 null, 433 "", 434 niceName, 435 description, 436 "", 437 "false"); 438 return propDef; 439 } 440 441 /** 442 * Helper method for getting the forbidden contexts from the resource manager without a try-catch block.<p> 443 * 444 * @return the forbidden context map 445 */ 446 protected Map<String, CmsDefaultSet<String>> safeGetAllowedContextMap() { 447 448 Map<String, CmsDefaultSet<String>> result = m_cachedContextMap; 449 if (result != null) { 450 return result; 451 } 452 try { 453 return OpenCms.getResourceManager().getAllowedContextMap(m_cms); 454 } catch (Exception e) { 455 LOG.error(e.getLocalizedMessage(), e); 456 return Collections.emptyMap(); 457 } 458 } 459 460 /** 461 * Updates the cached context map. 462 */ 463 void updateContextMap() { 464 465 try { 466 LOG.debug("Updating cached 'allowed template contexts' map."); 467 m_cachedContextMap = OpenCms.getResourceManager().getAllowedContextMap(m_cms); 468 LOG.debug("Finished updating cached 'allowed template contexts' map."); 469 } catch (Exception e) { 470 LOG.error(e.getLocalizedMessage(), e); 471 } 472 } 473}