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