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