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.jsp.util; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.formatters.CmsFormatterBeanParser; 032import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache; 033import org.opencms.file.CmsFile; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.types.I_CmsResourceType; 038import org.opencms.flex.CmsFlexController; 039import org.opencms.i18n.CmsLocaleManager; 040import org.opencms.jsp.CmsJspTagDisplay; 041import org.opencms.jsp.Messages; 042import org.opencms.main.CmsException; 043import org.opencms.main.CmsLog; 044import org.opencms.main.CmsRuntimeException; 045import org.opencms.main.OpenCms; 046import org.opencms.util.CmsStringUtil; 047import org.opencms.util.CmsUUID; 048import org.opencms.util.I_CmsMacroResolver; 049import org.opencms.xml.containerpage.CmsContainerElementBean; 050import org.opencms.xml.containerpage.CmsMacroFormatterBean; 051import org.opencms.xml.containerpage.I_CmsFormatterBean; 052import org.opencms.xml.content.CmsXmlContent; 053import org.opencms.xml.content.CmsXmlContentFactory; 054import org.opencms.xml.types.CmsXmlVfsFileValue; 055import org.opencms.xml.types.I_CmsXmlContentValue; 056 057import java.io.IOException; 058import java.util.HashMap; 059import java.util.List; 060import java.util.Map; 061 062import javax.servlet.http.HttpServletRequest; 063import javax.servlet.http.HttpServletResponse; 064import javax.servlet.jsp.PageContext; 065 066import org.apache.commons.beanutils.BeanUtilsBean; 067import org.apache.commons.beanutils.PropertyUtilsBean; 068import org.apache.commons.logging.Log; 069 070/** 071 * Resolver for macro formatters.<p> 072 */ 073public class CmsMacroFormatterResolver { 074 075 /** The parent macro key. */ 076 public static final String KEY_CMS = "cms."; 077 078 /** The element macro key. */ 079 public static final String KEY_ELEMENT = "element."; 080 081 /** The parent macro key. */ 082 public static final String KEY_PARENT = "parent."; 083 084 /** The settings macro key. */ 085 public static final String KEY_SETTINGS = "settings."; 086 087 /** Node name. */ 088 public static final String N_FORMATTER = "Formatter"; 089 090 /** Node name. */ 091 public static final String N_FORMATTERS = "Formatters"; 092 093 /** Node name. */ 094 public static final String N_MACRO = "Macro"; 095 096 /** Node name. */ 097 public static final String N_MACRO_NAME = "MacroName"; 098 099 /** The log object for this class. */ 100 private static final Log LOG = CmsLog.getLog(CmsMacroFormatterResolver.class); 101 102 /** The current cms context. */ 103 private CmsObject m_cms; 104 105 /** The page context. */ 106 private PageContext m_context; 107 108 /** The JSP context bean. */ 109 private CmsJspStandardContextBean m_contextBean; 110 111 /** The element to render. */ 112 private CmsContainerElementBean m_element; 113 114 /** The formatter references. */ 115 private Map<String, CmsUUID> m_formatterReferences; 116 117 /** The macro input string. */ 118 private String m_input; 119 120 /** The request. */ 121 private HttpServletRequest m_request; 122 123 /** The response. */ 124 private HttpServletResponse m_response; 125 126 /** 127 * Constructor.<p> 128 * 129 * @param context the page context 130 * @param req the request 131 * @param res the response 132 */ 133 public CmsMacroFormatterResolver(PageContext context, HttpServletRequest req, HttpServletResponse res) { 134 135 m_context = context; 136 m_request = req; 137 m_response = res; 138 CmsFlexController controller = CmsFlexController.getController(req); 139 if (controller == null) { 140 handleMissingFlexController(); 141 return; 142 } 143 m_cms = controller.getCmsObject(); 144 m_contextBean = CmsJspStandardContextBean.getInstance(m_request); 145 m_element = m_contextBean.getElement(); 146 } 147 148 /** 149 * Resolves the macro.<p> 150 * 151 * @throws IOException in case writing to the page context output stream fails 152 * @throws CmsException in case reading the macro settings fails 153 */ 154 @SuppressWarnings("resource") 155 public void resolve() throws IOException, CmsException { 156 157 initMacroContent(); 158 String input = getMacroInput(); 159 if (input == null) { 160 return; 161 } 162 if (input.length() < 3) { 163 // macro must have at last 3 chars "${}" or "%()" 164 m_context.getOut().print(input); 165 return; 166 } 167 168 int newDelimPos = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER); 169 int oldDelomPos = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER_OLD); 170 171 if ((oldDelomPos == -1) && (newDelimPos == -1)) { 172 // no macro delimiter found in input 173 m_context.getOut().print(input); 174 return; 175 } 176 177 int len = input.length(); 178 int nextDelimPos, delimPos1, delimPos2, endPos; 179 String macro; 180 char startChar, endChar; 181 int delimPos; 182 183 if ((oldDelomPos == -1) || ((newDelimPos > -1) && (newDelimPos < oldDelomPos))) { 184 delimPos = newDelimPos; 185 startChar = I_CmsMacroResolver.MACRO_START; 186 endChar = I_CmsMacroResolver.MACRO_END; 187 } else { 188 delimPos = oldDelomPos; 189 startChar = I_CmsMacroResolver.MACRO_START_OLD; 190 endChar = I_CmsMacroResolver.MACRO_END_OLD; 191 } 192 193 // append chars before the first delimiter found 194 m_context.getOut().print(input.substring(0, delimPos)); 195 do { 196 delimPos1 = delimPos + 1; 197 delimPos2 = delimPos1 + 1; 198 if (delimPos2 >= len) { 199 // remaining chars can't be a macro (minimum size is 3) 200 m_context.getOut().print(input.substring(delimPos, len)); 201 break; 202 } 203 // get the next macro delimiter 204 if ((newDelimPos > -1) && (newDelimPos < delimPos1)) { 205 newDelimPos = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER, delimPos1); 206 } 207 if ((oldDelomPos > -1) && (oldDelomPos < delimPos1)) { 208 oldDelomPos = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER_OLD, delimPos1); 209 } 210 if ((oldDelomPos == -1) && (newDelimPos == -1)) { 211 // none found, make sure remaining chars in this segment are appended 212 nextDelimPos = len; 213 } else { 214 // check if the next delimiter is old or new style 215 if ((oldDelomPos == -1) || ((newDelimPos > -1) && (newDelimPos < oldDelomPos))) { 216 nextDelimPos = newDelimPos; 217 } else { 218 nextDelimPos = oldDelomPos; 219 } 220 } 221 // check if the next char is a "macro start" 222 char start = input.charAt(delimPos1); 223 if (start == startChar) { 224 // we have a starting macro sequence "${" or "%(", now check if this segment contains a "}" or ")" 225 endPos = input.indexOf(endChar, delimPos); 226 if ((endPos > 0) && (endPos < nextDelimPos)) { 227 // this segment contains a closing macro delimiter "}" or "]", so we may have found a macro 228 macro = input.substring(delimPos2, endPos); 229 // resolve macro 230 try { 231 printMacroValue(macro); 232 } catch (Exception ex) { 233 LOG.error("Writing value for macro '" + macro + "' failed.", ex); 234 } 235 endPos++; 236 } else { 237 // no complete macro "${...}" or "%(...)" in this segment 238 endPos = delimPos; 239 } 240 } else { 241 // no macro start char after the "$" or "%" 242 endPos = delimPos; 243 } 244 // set macro style for next delimiter found 245 if (nextDelimPos == newDelimPos) { 246 startChar = I_CmsMacroResolver.MACRO_START; 247 endChar = I_CmsMacroResolver.MACRO_END; 248 } else { 249 startChar = I_CmsMacroResolver.MACRO_START_OLD; 250 endChar = I_CmsMacroResolver.MACRO_END_OLD; 251 } 252 // append the remaining chars after the macro to the start of the next macro 253 m_context.getOut().print(input.substring(endPos, nextDelimPos)); 254 delimPos = nextDelimPos; 255 } while (delimPos < len); 256 } 257 258 /** 259 * Returns the formatter bean for the given macro string, or <code>null</code> if none available.<p> 260 * 261 * @param macro the macro 262 * 263 * @return the formatter bean 264 */ 265 protected I_CmsFormatterBean getFormatterForMacro(String macro) { 266 267 CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache( 268 m_cms, 269 m_cms.getRequestContext().getRootUri()); 270 271 CmsUUID formatterId = null; 272 if (m_formatterReferences.containsKey(macro)) { 273 formatterId = m_formatterReferences.get(macro); 274 } else { 275 try { 276 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType( 277 CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG); 278 CmsResourceFilter filter = CmsResourceFilter.DEFAULT.addRequireType(type); 279 if (m_cms.existsResource(macro, filter)) { 280 CmsResource res = m_cms.readResource(macro); 281 formatterId = res.getStructureId(); 282 } 283 } catch (CmsException e) { 284 LOG.error("Failed to read formatter configuration.", e); 285 } 286 } 287 if (formatterId != null) { 288 return config.findFormatter(formatterId); 289 } 290 return null; 291 } 292 293 /** 294 * Returns the property value read from the given JavaBean. 295 * 296 * @param bean the JavaBean to read the property from 297 * @param property the property to read 298 * 299 * @return the property value read from the given JavaBean 300 */ 301 protected Object getMacroBeanValue(Object bean, String property) { 302 303 Object result = null; 304 if ((bean != null) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(property)) { 305 try { 306 PropertyUtilsBean propBean = BeanUtilsBean.getInstance().getPropertyUtils(); 307 result = propBean.getProperty(bean, property); 308 } catch (Exception e) { 309 LOG.error("Unable to access property '" + property + "' of '" + bean + "'.", e); 310 } 311 } else { 312 LOG.info("Invalid parameters: property='" + property + "' bean='" + bean + "'."); 313 } 314 return result; 315 } 316 317 /** 318 * Returns the macro input string.<p> 319 * 320 * @return the macro input string 321 */ 322 protected String getMacroInput() { 323 324 return m_input; 325 } 326 327 /** 328 * Prints the macro value to the output stream.<p> 329 * 330 * @param macro the macro string 331 * 332 * @throws IOException in case writing to the page context output stream fails 333 */ 334 @SuppressWarnings("resource") 335 protected void printMacroValue(String macro) throws IOException { 336 337 if (macro.startsWith(KEY_CMS)) { 338 Object result = getMacroBeanValue(m_contextBean, macro.substring(KEY_CMS.length())); 339 if (result != null) { 340 m_context.getOut().print(result); 341 } 342 } else if (macro.startsWith(KEY_ELEMENT)) { 343 Object result = getMacroBeanValue(m_contextBean.getElement(), macro.substring(KEY_ELEMENT.length())); 344 if (result != null) { 345 m_context.getOut().print(result); 346 } 347 } else if (macro.startsWith(KEY_PARENT)) { 348 Object result = getMacroBeanValue( 349 m_contextBean.getParentElement(m_element), 350 macro.substring(KEY_PARENT.length())); 351 if (result != null) { 352 m_context.getOut().print(result); 353 } 354 } else if (macro.startsWith(KEY_SETTINGS)) { 355 String settingValue = m_element.getSettings().get(macro.substring(KEY_SETTINGS.length())); 356 if (settingValue != null) { 357 m_context.getOut().print(settingValue); 358 } 359 } else { 360 361 I_CmsFormatterBean formatter = getFormatterForMacro(macro); 362 if (formatter != null) { 363 CmsContainerElementBean copy = CmsContainerElementBean.cloneWithFormatter( 364 m_element, 365 formatter.getJspStructureId()); 366 copy.setDoNotCache(true); 367 try { 368 CmsJspTagDisplay.displayAction(copy, formatter, m_context, m_request, m_response); 369 } catch (Exception e) { 370 LOG.error("Failed to display formatted content.", e); 371 } 372 } 373 } 374 } 375 376 /** 377 * This method is called when the flex controller can not be found during initialization.<p> 378 * 379 * Override this if you are reusing old workplace classes in a context where no flex controller is available. 380 */ 381 private void handleMissingFlexController() { 382 383 // controller not found - this request was not initialized properly 384 throw new CmsRuntimeException( 385 Messages.get().container(Messages.ERR_MISSING_CMS_CONTROLLER_1, CmsMacroFormatterResolver.class.getName())); 386 } 387 388 /** 389 * Initializes settings from the macro content.<p> 390 * 391 * @throws CmsException in case reading the settings fails 392 */ 393 private void initMacroContent() throws CmsException { 394 395 CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfigurationWithCache( 396 m_cms, 397 m_cms.getRequestContext().getRootUri()); 398 I_CmsFormatterBean formatterConfig = adeConfig.findFormatter(m_element.getFormatterId()); 399 if (formatterConfig instanceof CmsMacroFormatterBean) { 400 CmsMacroFormatterBean config = (CmsMacroFormatterBean)formatterConfig; 401 m_input = config.getMacroInput(); 402 m_formatterReferences = config.getReferencedFormatters(); 403 if (m_element.isInMemoryOnly()) { 404 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(config.getPlaceholderMacroInput())) { 405 m_input = config.getPlaceholderMacroInput(); 406 } 407 if (config.getDefaultContentStructureId() != null) { 408 try { 409 CmsResource defaultContent = m_cms.readResource( 410 ((CmsMacroFormatterBean)formatterConfig).getDefaultContentStructureId()); 411 CmsFile defaultFile = m_cms.readFile(defaultContent); 412 m_element = new CmsContainerElementBean( 413 defaultFile, 414 m_element.getFormatterId(), 415 m_element.getIndividualSettings(), 416 true, 417 m_element.editorHash(), 418 m_element.isCreateNew()); 419 } catch (CmsException e) { 420 LOG.error("Error reading default content for new resource", e); 421 } 422 } 423 } 424 } else { 425 // only as a fall back, should not be used 426 m_formatterReferences = new HashMap<String, CmsUUID>(); 427 CmsResource macroContent = m_cms.readResource(m_element.getFormatterId()); 428 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(m_cms, macroContent, m_request); 429 m_input = xmlContent.getStringValue(m_cms, CmsFormatterBeanParser.N_MACRO, CmsLocaleManager.MASTER_LOCALE); 430 List<I_CmsXmlContentValue> formatters = xmlContent.getValues( 431 CmsFormatterBeanParser.N_FORMATTERS, 432 CmsLocaleManager.MASTER_LOCALE); 433 for (I_CmsXmlContentValue formatterValue : formatters) { 434 CmsXmlVfsFileValue file = (CmsXmlVfsFileValue)xmlContent.getValue( 435 formatterValue.getPath() + "/" + CmsFormatterBeanParser.N_FORMATTER, 436 CmsLocaleManager.MASTER_LOCALE); 437 String macroName = xmlContent.getStringValue( 438 m_cms, 439 formatterValue.getPath() + "/" + CmsFormatterBeanParser.N_MACRO_NAME, 440 CmsLocaleManager.MASTER_LOCALE); 441 m_formatterReferences.put(macroName, file.getLink(m_cms).getStructureId()); 442 } 443 } 444 } 445}