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.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsResource; 034import org.opencms.flex.CmsFlexController; 035import org.opencms.i18n.CmsMessageContainer; 036import org.opencms.jsp.CmsJspTagEditable; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsRuntimeException; 039import org.opencms.main.OpenCms; 040import org.opencms.util.CmsCollectionsGenericWrapper; 041import org.opencms.util.CmsHtmlValidator; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.xml.containerpage.CmsContainerElementBean; 044import org.opencms.xml.containerpage.CmsFlexFormatterBean; 045import org.opencms.xml.containerpage.CmsMacroFormatterBean; 046import org.opencms.xml.containerpage.I_CmsFormatterBean; 047 048import java.io.IOException; 049import java.util.Date; 050import java.util.HashMap; 051import java.util.Locale; 052import java.util.Map; 053import java.util.Map.Entry; 054 055import javax.servlet.http.HttpServletRequest; 056import javax.servlet.jsp.PageContext; 057 058import org.stringtemplate.v4.DateRenderer; 059import org.stringtemplate.v4.ST; 060import org.stringtemplate.v4.STGroup; 061import org.stringtemplate.v4.compiler.CompiledST; 062import org.stringtemplate.v4.compiler.FormalArgument; 063 064/** 065 * Renderer for string templates.<p> 066 */ 067public class CmsStringTemplateRenderer { 068 069 /** The error display HTML. */ 070 public static final String ERROR_DISPLAY = "<div class='oc-fomatter-error'>\n" 071 + "<div class='oc-formatter-error-head'>%1$s</div>\n" 072 + "<div class='oc-formatter-error-body'>\n" 073 + "<div class='oc-formatter-error-source'>%2$s</div>\n" 074 + "<div class='oc-formatter-error-message'>%3$s</div>\n" 075 + "</div>\n</div>"; 076 077 /** The error display HTML, including error details. */ 078 public static final String ERROR_DISPLAY_WITH_DETAILS = "<div class='oc-fomatter-error'>\n" 079 + "<div class='oc-formatter-error-head'>%1$s</div>\n" 080 + "<div class='oc-formatter-error-body'>\n" 081 + "<div class='oc-formatter-error-source'>%2$s</div>\n" 082 + "<div class='oc-formatter-error-message'>%3$s</div>\n" 083 + "<div class='oc-formatter-error-details'><pre>%4$s</pre></div>\n" 084 + "</div>\n</div>"; 085 086 /** Key to access object function wrapper. */ 087 public static final String KEY_FUNCTIONS = "fn"; 088 089 /** Key to access element settings. */ 090 public static final String KEY_SETTINGS = "settings"; 091 092 /** The current cms context. */ 093 private CmsObject m_cms; 094 095 /** The page context. */ 096 private PageContext m_context; 097 098 /** The JSP context bean. */ 099 private CmsJspStandardContextBean m_contextBean; 100 101 /** The element to render. */ 102 private CmsContainerElementBean m_element; 103 104 /** The request. */ 105 private HttpServletRequest m_request; 106 107 /** 108 * Constructor.<p> 109 * 110 * @param context the page context 111 * @param req the request 112 */ 113 public CmsStringTemplateRenderer(PageContext context, HttpServletRequest req) { 114 115 m_context = context; 116 m_request = req; 117 CmsFlexController controller = CmsFlexController.getController(req); 118 if (controller == null) { 119 handleMissingFlexController(); 120 return; 121 } 122 m_cms = controller.getCmsObject(); 123 m_contextBean = CmsJspStandardContextBean.getInstance(m_request); 124 m_element = m_contextBean.getElement(); 125 } 126 127 /** 128 * Renders the given string template.<p> 129 * 130 * @param cms the cms context 131 * @param template the template 132 * @param content the content 133 * @param contextObjects additional context objects made available to the template 134 * 135 * @return the rendering result 136 */ 137 public static String renderTemplate( 138 CmsObject cms, 139 String template, 140 CmsJspContentAccessBean content, 141 Map<String, Object> contextObjects) { 142 143 return renderTemplateInternal(cms, template, content, contextObjects); 144 } 145 146 /** 147 * Renders the given string template.<p> 148 * 149 * @param cms the cms context 150 * @param template the template 151 * @param contentValue the content value (part of a content) 152 * @param contextObjects additional context objects made available to the template 153 * 154 * @return the rendering result 155 */ 156 public static String renderTemplate( 157 CmsObject cms, 158 String template, 159 CmsJspContentAccessValueWrapper contentValue, 160 Map<String, Object> contextObjects) { 161 162 return renderTemplateInternal(cms, template, contentValue, contextObjects); 163 } 164 165 /** 166 * Renders the given string template.<p> 167 * 168 * @param cms the cms context 169 * @param template the template 170 * @param content the content 171 * @param contextObjects additional context objects made available to the template 172 * 173 * @return the rendering result 174 */ 175 public static String renderTemplate( 176 CmsObject cms, 177 String template, 178 CmsResource content, 179 Map<String, Object> contextObjects) { 180 181 return renderTemplate(cms, template, new CmsJspContentAccessBean(cms, content), contextObjects); 182 } 183 184 /** 185 * Renders the given string template.<p> 186 * 187 * @param cms the cms context 188 * @param template the template 189 * @param content the content 190 * @param contextObjects additional context objects made available to the template 191 * @param pathPrefix the path to the content part that should be accessible as content to the string template. 192 * 193 * @return the rendering result 194 */ 195 public static String renderTemplate( 196 CmsObject cms, 197 String template, 198 CmsResource content, 199 Map<String, Object> contextObjects, 200 String pathPrefix) { 201 202 CmsJspContentAccessBean contentBean = new CmsJspContentAccessBean(cms, content); 203 return (null != pathPrefix) && !pathPrefix.isEmpty() 204 ? renderTemplate(cms, template, contentBean.getValue().get(pathPrefix), contextObjects) 205 : renderTemplate(cms, template, contentBean, contextObjects); 206 } 207 208 /** 209 * Wraps the element settings with access wrappers.<p> 210 * 211 * @param cms the current OpenCms user context 212 * @param settings the settings to wrap 213 * 214 * @return the element settings wrapped in access wrappers 215 */ 216 public static Map<String, CmsJspObjectValueWrapper> wrapSettings(CmsObject cms, Map<String, String> settings) { 217 218 Map<String, CmsJspObjectValueWrapper> wrappedSettings = null; 219 if (settings != null) { 220 wrappedSettings = new HashMap<String, CmsJspObjectValueWrapper>(settings.size()); 221 for (Entry<String, String> setting : settings.entrySet()) { 222 wrappedSettings.put(setting.getKey(), CmsJspObjectValueWrapper.createWrapper(cms, setting.getValue())); 223 } 224 } 225 return wrappedSettings; 226 } 227 228 /** 229 * Renders the given string template.<p> 230 * 231 * @param cms the cms context 232 * @param template the template 233 * @param content the content 234 * @param contextObjects additional context objects made available to the template 235 * 236 * @return the rendering result 237 */ 238 private static String renderTemplateInternal( 239 CmsObject cms, 240 String template, 241 Object content, 242 Map<String, Object> contextObjects) { 243 244 STGroup group = new STGroup('%', '%'); 245 group.registerRenderer(Date.class, new DateRenderer()); 246 CompiledST cST = group.defineTemplate("main", template); 247 cST.addArg(new FormalArgument("content")); 248 if (contextObjects != null) { 249 for (Entry<String, Object> entry : contextObjects.entrySet()) { 250 cST.addArg(new FormalArgument(entry.getKey())); 251 } 252 } 253 ST st = group.getInstanceOf("main"); 254 st.add("content", content); 255 if (contextObjects != null) { 256 for (Entry<String, Object> entry : contextObjects.entrySet()) { 257 st.add(entry.getKey(), entry.getValue()); 258 } 259 } 260 return st.render(cms.getRequestContext().getLocale()); 261 } 262 263 /** 264 * Renders the requested element content with the flex formatter string template.<p> 265 * 266 * @throws IOException in case writing to to page context out fails 267 */ 268 @SuppressWarnings("resource") 269 public void render() throws IOException { 270 271 CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfigurationWithCache( 272 m_cms, 273 m_cms.getRequestContext().getRootUri()); 274 I_CmsFormatterBean formatterConfig = adeConfig.findFormatter(m_element.getFormatterId()); 275 if (formatterConfig instanceof CmsFlexFormatterBean) { 276 CmsFlexFormatterBean config = (CmsFlexFormatterBean)formatterConfig; 277 String template = config.getStringTemplate(); 278 if (m_element.isInMemoryOnly()) { 279 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(config.getPlaceholderStringTemplate())) { 280 template = config.getPlaceholderStringTemplate(); 281 } 282 if (config.getDefaultContentStructureId() != null) { 283 try { 284 CmsResource defaultContent = m_cms.readResource( 285 ((CmsMacroFormatterBean)formatterConfig).getDefaultContentStructureId()); 286 CmsFile defaultFile = m_cms.readFile(defaultContent); 287 m_element = new CmsContainerElementBean( 288 defaultFile, 289 m_element.getFormatterId(), 290 m_element.getIndividualSettings(), 291 true, 292 m_element.editorHash(), 293 m_element.isCreateNew()); 294 } catch (CmsException e) { 295 // LOG.error("Error reading default content for new resource", e); 296 } 297 } 298 } 299 try { 300 Map<String, Object> context = new HashMap<String, Object>(); 301 context.put(KEY_SETTINGS, wrapSettings(m_cms, m_element.getSettings())); 302 context.put( 303 KEY_FUNCTIONS, 304 CmsCollectionsGenericWrapper.createLazyMap(new CmsObjectFunctionTransformer(m_cms))); 305 String output = renderTemplate(m_cms, template, m_element.getResource(), context); 306 if (CmsJspTagEditable.isEditableRequest(m_request)) { 307 CmsHtmlValidator validator = new CmsHtmlValidator(); 308 validator.validate(output); 309 if (!validator.isBalanced()) { 310 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms); 311 String messages = ""; 312 for (CmsMessageContainer message : validator.getMessages()) { 313 messages += message.key(locale) + "\n"; 314 } 315 output = String.format( 316 ERROR_DISPLAY_WITH_DETAILS, 317 Messages.get().getBundle(locale).key(Messages.GUI_FORMATTER_RENDERING_ERROR_0), 318 formatterConfig.getJspRootPath(), 319 Messages.get().getBundle(locale).key(Messages.GUI_FORMATTER_RENDERING_NOT_WELL_FORMED_0), 320 messages); 321 } else if (validator.getRootElementCount() > 1) { 322 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms); 323 output = String.format( 324 ERROR_DISPLAY, 325 Messages.get().getBundle(locale).key(Messages.GUI_FORMATTER_RENDERING_ERROR_0), 326 formatterConfig.getJspRootPath(), 327 Messages.get().getBundle(locale).key( 328 Messages.GUI_FORMATTER_RENDERING_MULTIPLE_ROOT_ELEMENTS_0)); 329 } 330 331 } 332 m_context.getOut().print(output); 333 } catch (Throwable t) { 334 if (CmsJspTagEditable.isEditableRequest(m_request)) { 335 String stackTrace = ""; 336 for (StackTraceElement element : t.getStackTrace()) { 337 stackTrace += element.toString() + "\n"; 338 } 339 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms); 340 m_context.getOut().println( 341 String.format( 342 ERROR_DISPLAY_WITH_DETAILS, 343 Messages.get().getBundle(locale).key(Messages.GUI_FORMATTER_RENDERING_ERROR_0), 344 formatterConfig.getJspRootPath(), 345 t.getMessage(), 346 stackTrace)); 347 } 348 } 349 } 350 } 351 352 /** 353 * This method is called when the flex controller can not be found during initialization.<p> 354 * 355 * Override this if you are reusing old workplace classes in a context where no flex controller is available. 356 */ 357 private void handleMissingFlexController() { 358 359 // controller not found - this request was not initialized properly 360 throw new CmsRuntimeException( 361 org.opencms.jsp.Messages.get().container( 362 org.opencms.jsp.Messages.ERR_MISSING_CMS_CONTROLLER_1, 363 CmsMacroFormatterResolver.class.getName())); 364 } 365 366}