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}