001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.workplace.commons;
029
030import org.opencms.jsp.CmsJspActionElement;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsIllegalArgumentException;
033import org.opencms.main.CmsIllegalStateException;
034import org.opencms.main.CmsLog;
035import org.opencms.util.CmsStringUtil;
036import org.opencms.util.CmsUUID;
037import org.opencms.workplace.CmsWorkplace;
038import org.opencms.workplace.list.A_CmsListDialog;
039
040import java.util.HashMap;
041import java.util.Iterator;
042import java.util.Map;
043
044import org.apache.commons.logging.Log;
045
046import jakarta.servlet.http.HttpServletRequest;
047import jakarta.servlet.http.HttpServletResponse;
048import jakarta.servlet.jsp.PageContext;
049
050/**
051 * This is a widget to be used in a dialog which should show a progress bar based
052 * on a list.<p>
053 *
054 * The progress bar uses Ajax to not reload the whole page. The code which runs
055 * inside the thread has to update the progress in the current thread.<p>
056 *
057 * The progress to be displayed is the progress of building large lists which can
058 * take some time until they are finished.<p>
059 *
060 * There is a progress bar shown with the percentages on the left. Additionaly it
061 * is possible to show a description above the progress bar.<p>
062 *
063 * @see A_CmsListDialog
064 *
065 * @since 7.0.0
066 */
067public class CmsProgressWidget {
068
069    /** The name of the key request parameter. */
070    public static final String PARAMETER_KEY = "progresskey";
071
072    /** The name of the refresh rate request parameter. */
073    public static final String PARAMETER_REFRESHRATE = "refreshrate";
074
075    /** The name of the show wait time request parameter. */
076    public static final String PARAMETER_SHOWWAITTIME = "showwaittime";
077
078    /** The time period after finished thread will be removed (10 min). */
079    private static final long CLEANUP_PERIOD = 10 * 60 * 1000;
080
081    /** The default width of the progress bar. */
082    private static final String DEFAULT_COLOR = "blue";
083
084    /** The default refresh rate (in ms) of the progress bar. */
085    private static final int DEFAULT_REFRESH_RATE = 2000;
086
087    /** The default width of the progress bar. */
088    private static final String DEFAULT_WIDTH = "200px";
089
090    /** The log object for this class. */
091    private static final Log LOG = CmsLog.getLog(CmsProgressWidget.class);
092
093    /** Map of running threads. */
094    private static Map<String, CmsProgressThread> m_threads = new HashMap<String, CmsProgressThread>();
095
096    /** The color of the progress bar. */
097    private String m_color;
098
099    /** The name of the JavaScript method to call after progress is finished. */
100    private String m_jsFinishMethod;
101
102    /** The current JSP action element. */
103    private CmsJspActionElement m_jsp;
104
105    /** The unique key of the thread belonging to this widget. */
106    private String m_key;
107
108    /** The time interval the progress gets refreshed (in ms). */
109    private int m_refreshRate;
110
111    /** The time period the show the wait symbol before the progress bar is shown.<p>
112     *  Set to 0 (zero) to disable this.<p> */
113    private int m_showWaitTime;
114
115    /** The width of the progress bar to use in HTML.<p> */
116    private String m_width;
117
118    /**
119     * Public constructor.<p>
120     *
121     * @param jsp an initialized JSP action element
122     */
123    public CmsProgressWidget(CmsJspActionElement jsp) {
124
125        m_jsp = jsp;
126
127        // set default values
128        m_width = DEFAULT_WIDTH;
129        m_color = DEFAULT_COLOR;
130
131        // find the show wait time from the request
132        m_showWaitTime = 0;
133        if (getJsp().getRequest().getParameter(PARAMETER_SHOWWAITTIME) != null) {
134            m_showWaitTime = Integer.valueOf(getJsp().getRequest().getParameter(PARAMETER_SHOWWAITTIME)).intValue();
135        }
136
137        // find the show wait time from the request
138        m_refreshRate = DEFAULT_REFRESH_RATE;
139        if (getJsp().getRequest().getParameter(PARAMETER_REFRESHRATE) != null) {
140            m_refreshRate = Integer.valueOf(getJsp().getRequest().getParameter(PARAMETER_REFRESHRATE)).intValue();
141        }
142
143        // find the key from the request
144        m_key = getJsp().getRequest().getParameter(PARAMETER_KEY);
145        if (m_key == null) {
146            // generate unique key
147            m_key = new CmsUUID().toString();
148        }
149    }
150
151    /**
152     * Public constructor with JSP variables.<p>
153     *
154     * @param context the JSP page context
155     * @param req the JSP request
156     * @param res the JSP response
157     */
158    public CmsProgressWidget(PageContext context, HttpServletRequest req, HttpServletResponse res) {
159
160        this(new CmsJspActionElement(context, req, res));
161    }
162
163    /**
164     * Returns the thread for the progress with the given key.<p>
165     *
166     * @param key the key of the thread
167     *
168     * @return the thread for the progress with the given key
169     */
170    public static CmsProgressThread getProgressThread(String key) {
171
172        return m_threads.get(key);
173    }
174
175    /**
176     * Removes the thread for the progress with the given key from the list with the actual threads.<p>
177     *
178     * @param key the key of the thread for the progress to remove from the list
179     */
180    public static void removeProgressThread(String key) {
181
182        m_threads.remove(key);
183    }
184
185    /**
186     * Returns the actual progress in percent.<p>
187     *
188     * The return value depends on the state of the progress/thread. This can be
189     * <ul>
190     * <li>the actual progress in percent with an optional description.</li>
191     * <li>the result as the html code for the list.</li>
192     * <li>an error message.</li>
193     * </ul><p>
194     *
195     * The result will be interpreted by the JavaScript method "updateProgressbar()".<p>
196     *
197     * @return the actual progress as a String
198     */
199    public String getActualProgress() {
200
201        try {
202            CmsProgressThread thread;
203            if (getProgressThread(getKey()) != null) {
204                thread = m_threads.get(getKey());
205
206                if (thread.isAlive()) {
207                    // wait the configured time until to update the progress the first time
208                    if (thread.getRuntime() < getShowWaitTime()) {
209                        while ((thread.getRuntime() < getShowWaitTime()) && (thread.isAlive())) {
210                            synchronized (this) {
211                                wait(500);
212                            }
213                        }
214                    } else {
215                        // wait the configured refresh rate before returning
216                        synchronized (this) {
217                            wait(getRefreshRate());
218                        }
219                    }
220                }
221
222                if (!thread.isAlive()) {
223                    // is an error occurred in the execution of the thread?
224                    if (thread.getError() != null) {
225                        return createError(
226                            Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
227                                Messages.GUI_PROGRESS_ERROR_IN_THREAD_0),
228                            thread.getError());
229                    }
230
231                    // return the result of the list created in the progress
232                    return thread.getResult();
233                }
234
235                // create and return the actual progress in percent with the description to be shown
236                StringBuffer result = new StringBuffer();
237
238                result.append("PRO");
239                result.append(thread.getProgress());
240                result.append("%");
241                result.append("|");
242                result.append(thread.getDescription());
243
244                return result.toString();
245            } else {
246                if (LOG.isErrorEnabled()) {
247                    LOG.error(Messages.get().getBundle().key(Messages.LOG_PROGRESS_THREAD_NOT_FOUND_1, getKey()));
248                }
249                return createError(
250                    Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
251                        Messages.GUI_PROGRESS_THREAD_NOT_FOUND_1,
252                        getKey()));
253            }
254
255        } catch (Throwable t) {
256            if (LOG.isErrorEnabled()) {
257                LOG.error(Messages.get().getBundle().key(Messages.LOG_PROGRESS_ERROR_CALC_PROGRESS_0), t);
258            }
259            return createError(
260                Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
261                    Messages.GUI_PROGRESS_ERROR_CALCULATING_0),
262                t);
263        }
264    }
265
266    /**
267     * Returns the color of the progress bar.<p>
268     *
269     * @return the color of the progress bar
270     */
271    public String getColor() {
272
273        return m_color;
274    }
275
276    /**
277     * Returns the name of the JavaScript method to call after progress is finished.<p>
278     *
279     * @return the name of the JavaScript method to call after progress is finished
280     */
281    public String getJsFinishMethod() {
282
283        return m_jsFinishMethod;
284    }
285
286    /**
287     * Generates the necessary JavaScript inclusion code for this widget.<p>
288     *
289     * @return the JavaScript inclusion code
290     */
291    public String getJsIncludes() {
292
293        StringBuffer result = new StringBuffer();
294
295        result.append("<script  src=\"");
296        result.append(CmsWorkplace.getSkinUri());
297        result.append("commons/ajax.js\"></script>\n");
298
299        result.append("<script >\n");
300
301        // initialize global variables
302        result.append("\tvar progressState = 0;\n");
303        result.append("\tvar progressResult = '';\n");
304
305        // function update progress bar
306        result.append("\tfunction updateProgressBar(msg, state) {\n");
307
308        // check state of progress
309        result.append("\t\tif (progressState != 1) {\n");
310        result.append("\t\t\tprogressState = 0;\n");
311        result.append("\t\t\treturn;\n");
312        result.append("\t\t}\n");
313
314        result.append("\t\tif (state == 'ok') {\n");
315
316        // get all elements to use
317        result.append("\t\t\tvar bar = document.getElementById(\"progressbar_bar\");\n");
318        result.append("\t\t\tvar percent = document.getElementById(\"progressbar_percent\");\n");
319        result.append("\t\t\tvar wait = document.getElementById(\"progressbar_wait\");\n");
320        result.append("\t\t\tvar desc = document.getElementById(\"progressbar_desc\");\n");
321
322        result.append("\t\t\tif (msg != \"\") {\n");
323
324        result.append("\t\t\t\tbar.parentNode.style.display = \"block\";\n");
325        result.append("\t\t\t\tpercent.style.display = \"inline\";\n");
326        result.append("\t\t\t\twait.style.display = \"none\";\n");
327        result.append("\t\t\t\tdesc.style.display = \"block\";\n");
328
329        // update progress
330        result.append("\t\t\t\tif (msg.substring(0,3) == \"PRO\") {\n");
331        result.append("\t\t\t\t\tvar splitted = msg.split(\"|\");\n");
332        result.append("\t\t\t\t\tbar.style.width = splitted[0].substr(3);\n");
333        result.append("\t\t\t\t\tpercent.innerHTML = splitted[0].substr(3);\n");
334        result.append("\t\t\t\t\tdesc.innerHTML = splitted[1];\n");
335        result.append("\t\t\t\t\tmakeRequest('");
336        result.append(getJsp().link("/system/workplace/commons/report-progress.jsp"));
337        result.append("', '");
338        result.append(PARAMETER_KEY);
339        result.append("=");
340        result.append(getKey());
341        result.append("&");
342        result.append(PARAMETER_SHOWWAITTIME);
343        result.append("=");
344        result.append(getShowWaitTime());
345        result.append("&");
346        result.append(PARAMETER_REFRESHRATE);
347        result.append("=");
348        result.append(getRefreshRate());
349        result.append("', 'updateProgressBar');\n");
350
351        // set error message
352        result.append("\t\t\t\t} else if (msg.substring(0,3) == \"ERR\") {\n");
353        result.append("\t\t\t\t\tsetProgressBarError(msg.substr(3));\n");
354
355        // set result
356        result.append("\t\t\t\t} else {\n");
357        result.append("\t\t\t\t\tprogressState = 0;\n");
358        result.append("\t\t\t\t\tbar.style.width = \"100%\";\n");
359        result.append("\t\t\t\t\tpercent.innerHTML = \"100%\";\n");
360        result.append("\t\t\t\t\tprogressResult = msg;\n");
361
362        result.append("\t\t\t\t\tbar.parentNode.style.display = \"none\";\n");
363        result.append("\t\t\t\t\tpercent.style.display = \"none\";\n");
364        result.append("\t\t\t\t\tdesc.style.display = \"none\";\n");
365        result.append("\t\t\t\t\twait.style.display = \"block\";\n");
366
367        result.append("\t\t\t\t\twindow.setTimeout(\"");
368        result.append(getJsFinishMethod());
369        result.append("()\",100);\n");
370        result.append("\t\t\t\t}\n");
371        result.append("\t\t\t} else {\n");
372        result.append("\t\t\t\tbar.style.width = \"100%\";\n");
373        result.append("\t\t\t}\n");
374
375        // fatal error returned by ajax
376        result.append("\t\t} else if (state == 'fatal') {\n");
377        result.append("\t\t\tprogressState = 0;\n");
378        result.append("\t\t\tsetProgressBarError(\"");
379        result.append(
380            org.opencms.workplace.Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
381                org.opencms.workplace.Messages.GUI_AJAX_REPORT_GIVEUP_0));
382        result.append("\");\n");
383
384        // error returned by ajax
385        result.append("\t\t} else if (state == 'error') {\n");
386        result.append("\t\t\tprogressState = 0;\n");
387        result.append("\t\t\tsetProgressBarError(\"");
388        result.append(
389            org.opencms.workplace.Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
390                org.opencms.workplace.Messages.GUI_AJAX_REPORT_ERROR_0));
391        result.append(" \" + msg);\n");
392
393        // wait returned by ajax -> display wait symbol
394        result.append("\t\t} else if (state == 'wait') {\n");
395        result.append("\t\t\tbar.parentNode.style.display = \"none\";\n");
396        result.append("\t\t\tpercent.style.display = \"none\";\n");
397        result.append("\t\t\twait.style.display = \"block\";\n");
398        result.append("\t\t}\n");
399        result.append("\t}\n");
400
401        // function set error
402        result.append("\tfunction setProgressBarError(msg) {\n");
403        result.append("\t\tvar error = document.getElementById(\"progressbar_error\");\n");
404        result.append("\t\tvar bar = document.getElementById(\"progressbar_bar\");\n");
405        result.append("\t\tvar percent = document.getElementById(\"progressbar_percent\");\n");
406        result.append("\t\tvar desc = document.getElementById(\"progressbar_desc\");\n");
407
408        result.append("\t\terror.innerHTML = msg;\n");
409        result.append("\t\terror.style.display = \"block\";\n");
410
411        result.append("\t\tbar.style.display = \"none\";\n");
412        result.append("\t\tpercent.style.display = \"none\";\n");
413        result.append("\t\tdesc.style.display = \"none\";\n");
414
415        result.append("\t}\n");
416
417        // function reset progress bar
418        result.append("\tfunction resetProgressBar() {\n");
419        result.append("\t\tvar bar = document.getElementById(\"progressbar_bar\");\n");
420        result.append("\t\tbar.parentNode.style.display = \"inline\";\n");
421        result.append("\t\tbar.style.width = \"0%\";\n");
422        result.append("\t\tbar.style.display = \"block\";\n");
423
424        result.append("\t\tvar percent = document.getElementById(\"progressbar_percent\");\n");
425        result.append("\t\tpercent.innerHTML = \"0%\";\n");
426        result.append("\t\tpercent.style.display = \"inline\";\n");
427
428        result.append("\t\tvar error = document.getElementById(\"progressbar_error\");\n");
429        result.append("\t\terror.innerHTML = \"\";\n");
430        result.append("\t\terror.style.display = \"none\";\n");
431
432        result.append("\t\tvar wait = document.getElementById(\"progressbar_wait\");\n");
433        result.append("\t\twait.style.display = \"none\";\n");
434
435        result.append("\t\tvar desc = document.getElementById(\"progressbar_desc\");\n");
436        result.append("\t\tdesc.style.display = \"block\";\n");
437        result.append("\t\tdesc.innerHTML = \"\";\n");
438
439        result.append("\t\t\t\t\tprogressResult = \"\";\n");
440
441        result.append("\t}\n");
442
443        // function start progress bar
444        result.append("\tfunction startProgressBar() {\n");
445        result.append("\t\tif (progressState > 0) {\n");
446        result.append("\t\t\tprogressState = 2;\n");
447        result.append("\t\t\twindow.setTimeout(\"startProgressBar()\",");
448        result.append(getRefreshRate());
449        result.append(");\n");
450        result.append("\t\t\treturn;\n");
451        result.append("\t\t}\n");
452        result.append("\t\tprogressState = 1;\n");
453        result.append("\t\tmakeRequest('");
454        result.append(getJsp().link("/system/workplace/commons/report-progress.jsp"));
455        result.append("', '");
456        result.append(PARAMETER_KEY);
457        result.append("=");
458        result.append(getKey());
459        result.append("&");
460        result.append(PARAMETER_SHOWWAITTIME);
461        result.append("=");
462        result.append(getShowWaitTime());
463        result.append("&");
464        result.append(PARAMETER_REFRESHRATE);
465        result.append("=");
466        result.append(getRefreshRate());
467        result.append("', 'updateProgressBar');\n");
468        result.append("\t}\n");
469
470        result.append("</script>\n");
471
472        return result.toString();
473    }
474
475    /**
476     * Returns the current JSP action element.<p>
477     *
478     * @return the the current JSP action element
479     */
480    public CmsJspActionElement getJsp() {
481
482        return m_jsp;
483    }
484
485    /**
486     * Returns the unique key of the thread belonging to this widget.<p>
487     *
488     * @return the unique key of the thread belonging to this widget
489     */
490    public String getKey() {
491
492        return m_key;
493    }
494
495    /**
496     * Returns the refresh rate in ms of the progress bar.<p>
497     *
498     * @return the refresh rate in ms of the progress bar
499     */
500    public int getRefreshRate() {
501
502        return m_refreshRate;
503    }
504
505    /**
506     * Returns the time period the show the wait symbol before the progress bar is shown.<p>
507     *
508     * @return the time period the show the wait symbol before the progress bar is shown
509     */
510    public int getShowWaitTime() {
511
512        return m_showWaitTime;
513    }
514
515    /**
516     * Generates the widget HTML for the progress bar.<p>
517     *
518     * @return the widget HTML for the progress bar
519     */
520    public String getWidget() {
521
522        StringBuffer result = new StringBuffer();
523
524        CmsProgressThread thread = getProgressThread(getKey());
525
526        // if the thread is finished before the widget is rendered
527        // show directly the result
528        if ((thread != null) && (!thread.isAlive())) {
529            result.append("<script >\n");
530            result.append("\tprogressState = 0;\n");
531            result.append("\tprogressResult = '");
532            result.append(CmsStringUtil.escapeJavaScript(getActualProgress()));
533            result.append("';\n");
534            result.append("\t");
535            result.append(getJsFinishMethod());
536            result.append("();\n");
537            result.append("</script>\n");
538        } else {
539            // check if to show the wait symbol
540            boolean showWait = false;
541            if (getShowWaitTime() > 0) {
542                // show if the thread is running and the time running is smaller than the configured
543                if ((thread != null) && (thread.isAlive()) && (thread.getRuntime() < getShowWaitTime())) {
544                    showWait = true;
545                } else if ((thread == null) && (getShowWaitTime() > 0)) {
546                    // show if there is no thread
547                    showWait = true;
548                }
549            }
550
551            result.append("<div id=\"progressbar_desc\" style=\"margin-bottom:5px;display:");
552            result.append(showWait ? "none" : "block");
553            result.append("\"></div>");
554
555            result.append("<div style=\"width:");
556            result.append(getWidth());
557            result.append(";border-width:1px;border-style:solid;padding:0px;margin:0px;float:left;display:");
558            result.append(showWait ? "none" : "inline");
559            result.append(";\">\n");
560            result.append("\t<div id=\"progressbar_bar\" style=\"width:0%;background-color:");
561            result.append(getColor());
562            result.append(";\">&nbsp;</div>\n");
563            result.append("</div>\n");
564            result.append("&nbsp;");
565            result.append("<div id=\"progressbar_percent\" style=\"display:");
566            result.append(showWait ? "none" : "inline");
567            result.append(";\" >0%</div>\n");
568
569            result.append(
570                "<div id=\"progressbar_error\" style=\"display:none;color:#B40000;font-weight:bold;\"></div>\n");
571
572            result.append("<div id=\"progressbar_wait\" style=\"display:");
573            result.append(showWait ? "block" : "none");
574            result.append(";color:#000099;font-weight:bold;\"><img src=\"");
575            result.append(CmsWorkplace.getSkinUri());
576            result.append("commons/wait.gif\" width='32' height='32' alt='' align='absmiddle' />");
577            result.append(
578                org.opencms.workplace.Messages.get().getBundle(getJsp().getRequestContext().getLocale()).key(
579                    org.opencms.workplace.Messages.GUI_AJAX_REPORT_WAIT_0));
580            result.append("</div>\n");
581
582            result.append("<script >\n");
583            result.append("\tstartProgressBar();\n");
584            result.append("</script>\n");
585        }
586
587        return result.toString();
588    }
589
590    /**
591     * Returns the width of the progress bar.<p>
592     *
593     * @return the width of the progress bar
594     */
595    public String getWidth() {
596
597        return m_width;
598    }
599
600    /**
601     * Sets the color of the progress bar.<p>
602     *
603     * @param color the color of the progress bar to set
604     */
605    public void setColor(String color) {
606
607        m_color = color;
608    }
609
610    /**
611     * Sets the name of the JavaScript method to call after progress is finished.<p>
612     *
613     * @param jsFinishMethod the name of the JavaScript method to call after progress is finished to set
614     */
615    public void setJsFinishMethod(String jsFinishMethod) {
616
617        m_jsFinishMethod = jsFinishMethod;
618    }
619
620    /**
621     * Sets the refresh rate in ms of the progress bar.<p>
622     *
623     * @param refreshRate the refresh rate in ms of the progress bar to set
624     */
625    public void setRefreshRate(int refreshRate) {
626
627        m_refreshRate = refreshRate;
628    }
629
630    /**
631     * Sets the time period the show the wait symbol before the progress bar is shown.<p>
632     *
633     * @param showWaitTime the time period the show the wait symbol before the progress bar is shown to set
634     */
635    public void setShowWaitTime(int showWaitTime) {
636
637        m_showWaitTime = showWaitTime;
638    }
639
640    /**
641     * Sets the width of the progress bar.<p>
642     *
643     * @param width the width of the progress bar to set
644     */
645    public void setWidth(String width) {
646
647        m_width = width;
648    }
649
650    /**
651     * Starts a thread for the progress on the given list.<p>
652     *
653     * @param list the list to use for the progress bar
654     */
655    public void startProgress(A_CmsListDialog list) {
656
657        startProgress(list, false);
658    }
659
660    /**
661     * Starts a thread for the progress on the given list.<p>
662     *
663     * @param list the list to use for the progress bar
664     * @param abortExisting if true then an already existing thread will be killed
665     */
666    public void startProgress(A_CmsListDialog list, boolean abortExisting) {
667
668        // check the list
669        if (list == null) {
670            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PROGRESS_START_INVALID_LIST_0));
671        }
672
673        // check if created key already exists
674        if (m_threads.get(getKey()) != null) {
675            if (abortExisting) {
676                if (LOG.isDebugEnabled()) {
677                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_PROGRESS_INTERRUPT_THREAD_1, getKey()));
678                }
679                Thread thread = m_threads.get(getKey());
680                thread.interrupt();
681            } else {
682                throw new CmsIllegalStateException(
683                    Messages.get().container(Messages.ERR_PROGRESS_START_THREAD_EXISTS_0));
684            }
685        }
686
687        // create the thread
688        CmsProgressThread thread = new CmsProgressThread(list, getKey(), list.getLocale());
689
690        Map<String, CmsProgressThread> threadsAbandoned = new HashMap<String, CmsProgressThread>();
691        Map<String, CmsProgressThread> threadsAlive = new HashMap<String, CmsProgressThread>();
692        synchronized (m_threads) {
693
694            // clean up abandoned threads
695            for (Iterator<Map.Entry<String, CmsProgressThread>> iter = m_threads.entrySet().iterator(); iter.hasNext();) {
696                Map.Entry<String, CmsProgressThread> entry = iter.next();
697                CmsProgressThread value = entry.getValue();
698
699                if ((!value.isAlive()) && ((System.currentTimeMillis() - value.getFinishTime()) > CLEANUP_PERIOD)) {
700                    threadsAbandoned.put(entry.getKey(), value);
701                } else {
702                    threadsAlive.put(entry.getKey(), value);
703                }
704            }
705
706            // add and start new thread
707            threadsAlive.put(thread.getKey(), thread);
708            thread.start();
709
710            m_threads = threadsAlive;
711        }
712
713        if (LOG.isDebugEnabled()) {
714            for (Iterator<String> iter = threadsAbandoned.keySet().iterator(); iter.hasNext();) {
715                LOG.debug(Messages.get().getBundle().key(Messages.LOG_PROGRESS_CLEAN_UP_THREAD_1, iter.next()));
716            }
717        }
718    }
719
720    /**
721     * Creates the html code for the given error message.<p>
722     *
723     * @param errorMsg the error message to place in the html code
724     *
725     * @return the html code for the error message
726     */
727    private String createError(String errorMsg) {
728
729        StringBuffer result = new StringBuffer();
730
731        result.append("ERR");
732        result.append(errorMsg);
733
734        return result.toString();
735    }
736
737    /**
738     * Creates the html code for the given error message and the
739     * provided Exception.<p>
740     *
741     * @param errorMsg the error message to place in the html code
742     * @param t the exception to add to the error message
743     *
744     * @return the html code for the error message
745     */
746    private String createError(String errorMsg, Throwable t) {
747
748        StringBuffer msg = new StringBuffer();
749        msg.append(errorMsg);
750        msg.append("\n");
751        msg.append(t.getMessage());
752        msg.append("\n");
753        msg.append(CmsException.getStackTraceAsString(t));
754
755        return createError(msg.toString());
756    }
757
758}