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.report;
029
030import org.opencms.i18n.CmsEncoder;
031import org.opencms.main.CmsException;
032import org.opencms.util.CmsStringUtil;
033
034import java.util.ArrayList;
035import java.util.List;
036import java.util.Locale;
037import java.util.StringTokenizer;
038
039/**
040 * HTML report output to be used for import / export / publish operations
041 * in the entire OpenCms system.<p>
042 *
043 * @since 6.0.0
044 */
045public class CmsHtmlReport extends A_CmsReport {
046
047    /** Constant for a HTML linebreak with added "real" line break. */
048    public static final String LINEBREAK = "<br>";
049
050    /**
051     * Constant for a HTML linebreak with added "real" line break-
052     * traditional style for report threads that still use XML templates for their output.
053     */
054    public static final String LINEBREAK_TRADITIONAL = "<br>\\n";
055
056    /** The list of report objects e.g. String, CmsPageLink, Exception ... */
057    private List<Object> m_content;
058
059    /**
060     * Counter to remember what is already shown,
061     * indicates the next index of the m_content list that has to be reported.
062     */
063    private int m_indexNext;
064
065    /** Flag to indicate if an exception should be displayed long or short. */
066    private boolean m_showExceptionStackTrace;
067
068    /** If set to <code>true</code> nothing is kept in memory. */
069    private boolean m_transient;
070
071    /** Boolean flag indicating whether this report should generate HTML or JavaScript output. */
072    private boolean m_writeHtml;
073
074    /**
075     * Constructs a new report using the provided locale for the output language.<p>
076     *
077     * @param locale the locale to use for the output language
078     * @param siteRoot the site root of the user who started this report (may be <code>null</code>)
079     */
080    public CmsHtmlReport(Locale locale, String siteRoot) {
081
082        this(locale, siteRoot, false, false);
083    }
084
085    /**
086     * Constructs a new report using the provided locale for the output language.<p>
087     *
088     * @param locale the locale to use for the output language
089     * @param siteRoot the site root of the user who started this report (may be <code>null</code>)
090     * @param writeHtml if <code>true</code>, this report should generate HTML instead of JavaScript output
091     * @param isTransient If set to <code>true</code> nothing is kept in memory
092     */
093    public CmsHtmlReport(Locale locale, String siteRoot, boolean writeHtml, boolean isTransient) {
094
095        init(locale, siteRoot);
096        m_content = new ArrayList<Object>(256);
097        m_showExceptionStackTrace = true;
098        m_writeHtml = writeHtml;
099        m_transient = isTransient;
100    }
101
102    /**
103     * @see org.opencms.report.I_CmsReport#getReportUpdate()
104     */
105    public synchronized String getReportUpdate() {
106
107        StringBuffer result = new StringBuffer();
108        int indexEnd = m_content.size();
109        for (int i = m_indexNext; i < indexEnd; i++) {
110            int pos = m_transient ? 0 : i;
111            Object obj = m_content.get(pos);
112            if ((obj instanceof String) || (obj instanceof StringBuffer)) {
113                result.append(obj);
114            } else if (obj instanceof Throwable) {
115                result.append(getExceptionElement((Throwable)obj));
116            }
117            if (m_transient) {
118                m_content.remove(m_indexNext);
119            }
120        }
121        m_indexNext = m_transient ? 0 : indexEnd;
122        return result.toString();
123    }
124
125    /**
126     * Returns if the report writes html or javascript code.<p>
127     *
128     * @return <code>true</code> if the report writes html, and <code>false</code> if the report writes javascript code
129     */
130    public boolean isWriteHtml() {
131
132        return m_writeHtml;
133    }
134
135    /**
136     * @see org.opencms.report.A_CmsReport#print(java.lang.String, int)
137     */
138    @Override
139    public synchronized void print(String value, int format) {
140
141        StringBuffer buf = null;
142
143        if (!m_writeHtml) {
144            value = CmsStringUtil.escapeJavaScript(value);
145            switch (format) {
146                case FORMAT_HEADLINE:
147                    buf = new StringBuffer();
148                    buf.append("aH('");
149                    buf.append(value);
150                    buf.append("'); ");
151                    break;
152                case FORMAT_WARNING:
153                    buf = new StringBuffer();
154                    buf.append("aW('");
155                    buf.append(value);
156                    buf.append("'); ");
157                    addWarning(value);
158                    break;
159                case FORMAT_ERROR:
160                    buf = new StringBuffer();
161                    buf.append("aE('");
162                    buf.append(value);
163                    buf.append("'); ");
164                    addError(value);
165                    break;
166                case FORMAT_NOTE:
167                    buf = new StringBuffer();
168                    buf.append("aN('");
169                    buf.append(value);
170                    buf.append("'); ");
171                    break;
172                case FORMAT_OK:
173                    buf = new StringBuffer();
174                    buf.append("aO('");
175                    buf.append(value);
176                    buf.append("'); ");
177                    break;
178                case FORMAT_DEFAULT:
179                default:
180                    buf = new StringBuffer();
181                    buf.append("a('");
182                    buf.append(value);
183                    buf.append("'); ");
184            }
185            // the output lines get split back into single lines on the client-side.
186            // thus, a separate JavaScript call has to be added here to tell the
187            // client that we want a linebreak here...
188            if (value.trim().endsWith(getLineBreak())) {
189                buf.append("aB(); ");
190            }
191            m_content.add(buf.toString());
192        } else {
193            switch (format) {
194                case FORMAT_HEADLINE:
195                    buf = new StringBuffer();
196                    buf.append("<span class='head'>");
197                    buf.append(value);
198                    buf.append("</span>");
199                    break;
200                case FORMAT_WARNING:
201                    buf = new StringBuffer();
202                    buf.append("<span class='warn'>");
203                    buf.append(value);
204                    buf.append("</span>");
205                    addWarning(value);
206                    break;
207                case FORMAT_ERROR:
208                    buf = new StringBuffer();
209                    buf.append("<span class='err'>");
210                    buf.append(value);
211                    buf.append("</span>");
212                    addError(value);
213                    break;
214                case FORMAT_NOTE:
215                    buf = new StringBuffer();
216                    buf.append("<span class='note'>");
217                    buf.append(value);
218                    buf.append("</span>");
219                    break;
220                case FORMAT_OK:
221                    buf = new StringBuffer();
222                    buf.append("<span class='ok'>");
223                    buf.append(value);
224                    buf.append("</span>");
225                    break;
226                case FORMAT_DEFAULT:
227                default:
228                    buf = new StringBuffer(value);
229            }
230            if (value.trim().endsWith(getLineBreak())) {
231                buf.append("\n");
232            }
233            m_content.add(buf.toString());
234        }
235        setLastEntryTime(System.currentTimeMillis());
236    }
237
238    /**
239     * @see org.opencms.report.I_CmsReport#println()
240     */
241    public void println() {
242
243        print(getLineBreak());
244    }
245
246    /**
247     * @see org.opencms.report.I_CmsReport#println(java.lang.Throwable)
248     */
249    public synchronized void println(Throwable t) {
250
251        addError(t.getMessage());
252        m_content.add(t);
253        setLastEntryTime(System.currentTimeMillis());
254    }
255
256    /**
257     * Returns the correct line break notation depending on the output style of this report.
258     *
259     * @return the correct line break notation
260     */
261    protected String getLineBreak() {
262
263        return m_writeHtml ? LINEBREAK_TRADITIONAL : LINEBREAK;
264    }
265
266    /**
267     * Output helper method to format a reported {@link Throwable} element.<p>
268     *
269     * This method ensures that exception stack traces are properly escaped
270     * when they are added to the report.<p>
271     *
272     * There is a member variable {@link #m_showExceptionStackTrace} in this
273     * class that controls if the stack track is shown or not.
274     * In a later version this might be configurable on a per-user basis.<p>
275     *
276     * @param throwable the exception to format
277     *
278     * @return the formatted StringBuffer
279     */
280    private StringBuffer getExceptionElement(Throwable throwable) {
281
282        StringBuffer buf = new StringBuffer(256);
283
284        if (!m_writeHtml) {
285            if (m_showExceptionStackTrace) {
286                buf.append("aT('");
287                buf.append(getMessages().key(Messages.RPT_EXCEPTION_0));
288                String exception = CmsEncoder.escapeXml(CmsException.getStackTraceAsString(throwable));
289                StringBuffer excBuffer = new StringBuffer(exception.length() + 50);
290                StringTokenizer tok = new StringTokenizer(exception, "\r\n");
291                while (tok.hasMoreTokens()) {
292                    excBuffer.append(tok.nextToken());
293                    excBuffer.append(getLineBreak());
294                }
295                buf.append(CmsStringUtil.escapeJavaScript(excBuffer.toString()));
296                buf.append("'); ");
297            } else {
298                buf.append("aT('");
299                buf.append(getMessages().key(Messages.RPT_EXCEPTION_0));
300                buf.append(CmsStringUtil.escapeJavaScript(throwable.toString()));
301                buf.append("'); ");
302            }
303            m_content.add(buf);
304        } else {
305            if (m_showExceptionStackTrace) {
306                buf.append("<span class='throw'>");
307                buf.append(getMessages().key(Messages.RPT_EXCEPTION_0));
308                String exception = CmsEncoder.escapeXml(CmsException.getStackTraceAsString(throwable));
309                StringBuffer excBuffer = new StringBuffer(exception.length() + 50);
310                StringTokenizer tok = new StringTokenizer(exception, "\r\n");
311                while (tok.hasMoreTokens()) {
312                    excBuffer.append(tok.nextToken());
313                    excBuffer.append(getLineBreak());
314                }
315                buf.append(excBuffer.toString());
316                buf.append("</span>");
317            } else {
318                buf.append("<span class='throw'>");
319                buf.append(getMessages().key(Messages.RPT_EXCEPTION_0));
320                buf.append(throwable.toString());
321                buf.append("</span>");
322                buf.append(getLineBreak());
323            }
324        }
325        return buf;
326    }
327}