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