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