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 GmbH & Co. KG, 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;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsRequestContext;
032import org.opencms.flex.CmsFlexController;
033import org.opencms.jsp.parse.A_CmsConfiguredHtmlParser;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.util.CmsStringUtil;
037
038import java.io.PrintWriter;
039import java.io.StringWriter;
040import java.util.Iterator;
041import java.util.List;
042
043import javax.servlet.ServletRequest;
044import javax.servlet.jsp.JspException;
045import javax.servlet.jsp.PageContext;
046import javax.servlet.jsp.tagext.BodyTagSupport;
047
048import org.apache.commons.logging.Log;
049
050import org.htmlparser.util.ParserException;
051
052/**
053 * Implements the <code>&lt;cms:parse&gt;&lt;/cms:parse&gt;</code> tag to allow parsing of nested
054 * HTML with the {@link org.opencms.jsp.parse.A_CmsConfiguredHtmlParser}} implementation specified by the "parserClass" attribute.
055 * <p>
056 *
057 * @since 6.1.3
058 */
059public class CmsJspTagParse extends BodyTagSupport {
060
061    /**
062     * The name of the mandatory Tag attribute for the visitor class an instance of will be guided
063     * throught the body content.
064     */
065    public static final String ATT_VISITOR_CLASS = "parserClass";
066
067    /** Tag name constant for log output. */
068    public static final String TAG_NAME = "parse";
069
070    /** The log object for this class. */
071    private static final Log LOG = CmsLog.getLog(CmsJspTagParse.class);
072
073    /** Serial version UID required for safe serialization. */
074    private static final long serialVersionUID = -6541745426202242240L;
075
076    /** Indicates the parse action should be disabled to allow inline editing in the container page editor. */
077    private boolean m_allowInlineEdit;
078
079    /** The visitor / parser class name to use. */
080    private String m_configuredParserClassname;
081
082    /** List of upper case tag name strings of tags that should not be auto-corrected if closing divs are missing. */
083    private List<String> m_noAutoCloseTags;
084
085    /** The attribute value of the param attribute. */
086    private String m_param = "";
087
088    /**
089     * @see javax.servlet.jsp.tagext.Tag#doEndTag()
090     *
091     * @return EVAL_PAGE
092     *
093     * @throws JspException in case something goes wrong
094     */
095    @Override
096    public int doEndTag() throws JspException {
097
098        ServletRequest req = pageContext.getRequest();
099        A_CmsConfiguredHtmlParser parser;
100        if (m_allowInlineEdit && CmsJspTagEditable.isEditableRequest(req)) {
101            // during inline editing the content should not be parsed
102            try {
103                getBodyContent().writeOut(pageContext.getOut());
104                release();
105            } catch (Exception ex) {
106                release();
107                if (LOG.isErrorEnabled()) {
108                    LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, TAG_NAME), ex);
109                }
110                // this is severe
111                throw new JspException(ex);
112            }
113        } else {
114            // This will always be true if the page is called through OpenCms
115            if (CmsFlexController.isCmsRequest(req)) {
116                String content = "";
117                try {
118                    if (CmsStringUtil.isEmpty(m_configuredParserClassname)) {
119                        if (LOG.isErrorEnabled()) {
120                            LOG.error(
121                                Messages.get().getBundle().key(
122                                    Messages.GUI_ERR_TAG_ATTRIBUTE_MISSING_2,
123                                    new Object[] {TAG_NAME, ATT_VISITOR_CLASS}));
124                        }
125
126                    }
127                    // wrong attribute visitorClass -> content will remain empty, but no exception is
128                    // thrown
129                    try {
130                        // load
131                        Class<?> cl = Class.forName(m_configuredParserClassname);
132                        // Instantiate
133                        Object instance = cl.newInstance();
134                        // cast
135                        parser = (A_CmsConfiguredHtmlParser)instance;
136                        parser.setParam(m_param);
137                        // cms object:
138                        CmsFlexController controller = CmsFlexController.getController(req);
139                        CmsObject cms = controller.getCmsObject();
140                        parser.setCmsObject(cms);
141                        content = parseTagAction(getBodyContent().getString(), pageContext, parser);
142
143                    } catch (Exception e) {
144                        if (LOG.isErrorEnabled()) {
145                            LOG.error(
146                                Messages.get().getBundle().key(
147                                    Messages.GUI_ERR_TAG_ATTRIBUTE_INVALID_3,
148                                    new Object[] {
149                                        TAG_NAME,
150                                        ATT_VISITOR_CLASS,
151                                        A_CmsConfiguredHtmlParser.class.getName()}),
152                                e);
153                        }
154                        e.printStackTrace(System.err);
155                    }
156
157                } finally {
158                    try {
159                        getBodyContent().clear();
160                        getBodyContent().print(content);
161                        getBodyContent().writeOut(pageContext.getOut());
162                        release();
163
164                    } catch (Exception ex) {
165                        release();
166                        if (LOG.isErrorEnabled()) {
167                            LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, TAG_NAME), ex);
168                        }
169                        // this is severe
170                        throw new JspException(ex);
171                    }
172
173                }
174
175            }
176        }
177        return EVAL_PAGE;
178    }
179
180    /**
181     * Getter for the attribute "noAutoCloseTags" of the &lt;cms:parse&gt; tag.<p>
182     *
183     * Returns a <code>String</code> that consists of the comma-separated upper case tag names for which this
184     * tag will not correct missing closing tags. <p>
185     *
186     * @return a String that consists of the comma-separated upper case tag names for which this
187     *      tag will not correct missing closing tags.
188     */
189    public String getNoAutoCloseTags() {
190
191        StringBuffer result = new StringBuffer();
192        if ((m_noAutoCloseTags != null) && (m_noAutoCloseTags.size() > 0)) {
193            Iterator<String> it = m_noAutoCloseTags.iterator();
194            while (it.hasNext()) {
195                result.append(it.next()).append(',');
196            }
197        }
198        return result.toString();
199    }
200
201    /**
202     * Returns the param.<p>
203     *
204     * @return the param
205     */
206    public String getParam() {
207
208        return m_param;
209    }
210
211    /**
212     * Returns the fully qualified class name of the {@link A_CmsConfiguredHtmlParser} class to use
213     * for parsing.<p>
214     *
215     * @return the parserrClass
216     */
217    public String getParserClass() {
218
219        return m_configuredParserClassname;
220    }
221
222    /**
223     * Returns if the parse action should be disabled to allow inline editing in the container page editor.<p>
224     *
225     * @return <code>true</code> if the parse action should be disabled to allow inline editing in the container page editor
226     */
227    public boolean isAllowInlineEdit() {
228
229        return m_allowInlineEdit;
230    }
231
232    /**
233     * Internal action method.<p>
234     *
235     * Parses (and potentially transforms) a HTMl content block.<p>
236     *
237     * @param content the content to be parsed / transformed
238     * @param context needed for getting the encoding / the locale
239     * @param parser the visitor / parser to use
240     *
241     * @return the transformed content
242     */
243    public String parseTagAction(String content, PageContext context, A_CmsConfiguredHtmlParser parser) {
244
245        String result = null;
246        CmsRequestContext cmsContext = CmsFlexController.getCmsObject(context.getRequest()).getRequestContext();
247
248        if (parser == null) {
249            if (LOG.isErrorEnabled()) {
250                LOG.error(
251                    Messages.get().getBundle(cmsContext.getLocale()).key(
252                        Messages.GUI_ERR_TAG_ATTRIBUTE_MISSING_2,
253                        new Object[] {TAG_NAME, ATT_VISITOR_CLASS}));
254            }
255            result = content;
256        } else {
257
258            String encoding = cmsContext.getEncoding();
259            try {
260                result = parser.doParse(content, encoding, m_noAutoCloseTags);
261
262            } catch (ParserException pex) {
263
264                if (LOG.isErrorEnabled()) {
265                    LOG.error(
266                        Messages.get().getBundle(cmsContext.getLocale()).key(
267                            Messages.ERR_PROCESS_TAG_1,
268                            new Object[] {TAG_NAME}),
269                        pex);
270                }
271                StringWriter stackTrace = new StringWriter();
272                PrintWriter writer = new PrintWriter(new StringWriter());
273                StringBuffer msg = new StringBuffer("<!--\n").append(pex.getLocalizedMessage()).append("\n");
274                pex.printStackTrace(writer);
275                msg.append(stackTrace.toString()).append("\n-->");
276                result = msg.toString();
277            } catch (CmsException cmex) {
278                if (LOG.isErrorEnabled()) {
279                    LOG.error(
280                        Messages.get().getBundle(cmsContext.getLocale()).key(
281                            Messages.ERR_PROCESS_TAG_1,
282                            new Object[] {TAG_NAME}),
283                        cmex);
284                }
285                StringWriter stackTrace = new StringWriter();
286                PrintWriter writer = new PrintWriter(new StringWriter());
287                StringBuffer msg = new StringBuffer("<!--\n").append(cmex.getLocalizedMessage()).append("\n");
288                cmex.printStackTrace(writer);
289                msg.append(stackTrace.toString()).append("\n-->");
290                result = msg.toString();
291            }
292
293        }
294        return result;
295    }
296
297    /**
298     * @see javax.servlet.jsp.tagext.Tag#release()
299     */
300    @Override
301    public void release() {
302
303        m_configuredParserClassname = null;
304        m_param = null;
305        super.release();
306    }
307
308    /**
309     * Sets if the parse action should be disabled to allow inline editing in the container page editor.<p>
310     *
311     * @param allowInlineEdit <code>true</code> to allow inline editing
312     */
313    public void setAllowInlineEdit(boolean allowInlineEdit) {
314
315        m_allowInlineEdit = allowInlineEdit;
316    }
317
318    /**
319     * Setter for the attribute "noAutoCloseTags" of the &lt;cms:parse&gt; tag.<p>
320     *
321     * Awaits a <code>String</code> that consists of the comma-separated upper case tag names for which this
322     * tag should not correct missing closing tags.<p>
323     *
324     * @param noAutoCloseTagList a <code>String</code> that consists of the comma-separated upper case tag names for which this
325     *      tag should not correct missing closing tags
326     */
327    public void setNoAutoCloseTags(String noAutoCloseTagList) {
328
329        m_noAutoCloseTags = CmsStringUtil.splitAsList(noAutoCloseTagList, ',');
330
331    }
332
333    /**
334     * Sets the param.<p>
335     *
336     * @param param the param to set
337     */
338    public void setParam(String param) {
339
340        m_param = param;
341    }
342
343    /**
344     * Sets the fully qualified class name of the {@link A_CmsConfiguredHtmlParser} class to use for
345     * parsing.<p>
346     *
347     * @param parserClass the fully qualified class name of the {@link A_CmsConfiguredHtmlParser}
348     *            class to use for parsing
349     */
350    public void setParserClass(String parserClass) {
351
352        m_configuredParserClassname = parserClass;
353    }
354}