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.list;
029
030import org.opencms.i18n.CmsMessageContainer;
031import org.opencms.jsp.CmsJspActionElement;
032import org.opencms.main.CmsException;
033import org.opencms.main.CmsLog;
034import org.opencms.main.CmsRuntimeException;
035import org.opencms.util.CmsCollectionsGenericWrapper;
036import org.opencms.util.CmsStringUtil;
037import org.opencms.workplace.CmsDialog;
038import org.opencms.workplace.CmsWorkplaceSettings;
039import org.opencms.workplace.tools.CmsToolDialog;
040
041import java.io.IOException;
042import java.util.ArrayList;
043import java.util.HashMap;
044import java.util.Hashtable;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048
049import org.apache.commons.logging.Log;
050
051import jakarta.servlet.ServletException;
052import jakarta.servlet.http.HttpServletRequest;
053import jakarta.servlet.jsp.JspException;
054import jakarta.servlet.jsp.JspWriter;
055
056/**
057 * Provides a dialog with a list widget.<p>
058 *
059 * @since 6.0.0
060 */
061public abstract class A_CmsListDialog extends CmsDialog {
062
063    /** Value for the action: execute a list item independent action of the list. */
064    public static final int ACTION_LIST_INDEPENDENT_ACTION = 83;
065
066    /** Value for the action: execute an multi action of the list. */
067    public static final int ACTION_LIST_MULTI_ACTION = 85;
068
069    /** Value for the action: search the list. */
070    public static final int ACTION_LIST_SEARCH = 81;
071
072    /** Value for the action: go to a page. */
073    public static final int ACTION_LIST_SELECT_PAGE = 82;
074
075    /** Value for the action: execute a single action of the list. */
076    public static final int ACTION_LIST_SINGLE_ACTION = 84;
077
078    /** Value for the action: sort the list. */
079    public static final int ACTION_LIST_SORT = 80;
080
081    /** Standard list button location. */
082    public static final String ICON_ACTIVE = "list/active.png";
083
084    /** Standard list button location. */
085    public static final String ICON_ADD = "list/add.png";
086
087    /** Standard list button location. */
088    public static final String ICON_DELETE = "list/delete.png";
089
090    /** Standard list button location. */
091    public static final String ICON_DETAILS_HIDE = "list/details_hide.png";
092
093    /** Standard list button location. */
094    public static final String ICON_DETAILS_SHOW = "list/details_show.png";
095
096    /** Standard list button location. */
097    public static final String ICON_DISABLED = "list/disabled.png";
098
099    /** Standard list button location. */
100    public static final String ICON_INACTIVE = "list/inactive.png";
101
102    /** Standard list button location. */
103    public static final String ICON_MINUS = "list/minus.png";
104
105    /** Standard list button location. */
106    public static final String ICON_MULTI_ACTIVATE = "list/multi_activate.png";
107
108    /** Standard list button location. */
109    public static final String ICON_MULTI_ADD = "list/multi_add.png";
110
111    /** Standard list button location. */
112    public static final String ICON_MULTI_DEACTIVATE = "list/multi_deactivate.png";
113
114    /** Standard list button location. */
115    public static final String ICON_MULTI_DELETE = "list/multi_delete.png";
116
117    /** Standard list button location. */
118    public static final String ICON_MULTI_MINUS = "list/multi_minus.png";
119
120    /** Request parameter value for the list action: a list item independent action has been triggered. */
121    public static final String LIST_INDEPENDENT_ACTION = "listindependentaction";
122
123    /** Request parameter value for the list action: a multi action has been triggered. */
124    public static final String LIST_MULTI_ACTION = "listmultiaction";
125
126    /** Request parameter value for the list action: search/filter. */
127    public static final String LIST_SEARCH = "listsearch";
128
129    /** Request parameter value for the list action: select a page. */
130    public static final String LIST_SELECT_PAGE = "listselectpage";
131
132    /** Request parameter value for the list action: a single action has been triggered. */
133    public static final String LIST_SINGLE_ACTION = "listsingleaction";
134
135    /** Request parameter value for the list action: sort. */
136    public static final String LIST_SORT = "listsort";
137
138    /** Request parameter key for the requested page. */
139    public static final String PARAM_FORMNAME = "formname";
140
141    /** Request parameter key for the list action. */
142    public static final String PARAM_LIST_ACTION = "listaction";
143
144    /** Request parameter key for the requested page. */
145    public static final String PARAM_PAGE = "page";
146
147    /** Request parameter key for search the filter. */
148    public static final String PARAM_SEARCH_FILTER = "searchfilter";
149
150    /** Request parameter key for the selected item(s). */
151    public static final String PARAM_SEL_ITEMS = "selitems";
152
153    /** Request parameter key for the column to sort the list. */
154    public static final String PARAM_SORT_COL = "sortcol";
155
156    /** The log object for this class. */
157    private static final Log LOG = CmsLog.getLog(A_CmsListDialog.class);
158
159    public static String KEY_META_DATA_CACHE = "key_meta_data_cache";
160
161    /** metadata map for all used list metadata objects. */
162    private Map<String, CmsListMetadata> m_metadatas;
163
164    /** A flag which indicates whether the list should use database paging (only supported for some lists) .**/
165    protected boolean m_lazy;
166
167    /** Activation decision Flag. */
168    private boolean m_active;
169
170    /** the internal list. */
171    private CmsHtmlList m_list;
172
173    /** The id of the list. */
174    private String m_listId;
175
176    /** Cached List state in case of {@link #refreshList()} method call. */
177    private CmsListState m_listState;
178
179    /** The displayed page. */
180    private String m_paramFormName;
181
182    /** The list action. */
183    private String m_paramListAction;
184
185    /** The displayed page. */
186    private String m_paramPage;
187
188    /** The search filter text. */
189    private String m_paramSearchFilter;
190
191    /** The selected items, comma separated list. */
192    private String m_paramSelItems;
193
194    /** The column to sort the list. */
195    private String m_paramSortCol;
196
197    /** The column to search the list. */
198    private String m_searchColId;
199
200    /**
201     * Public constructor.<p>
202     *
203     * @param jsp an initialized JSP action element
204     * @param listId the id of the displayed list
205     * @param listName the name of the list
206     * @param sortedColId the a priory sorted column
207     * @param sortOrder the order of the sorted column
208     * @param searchableColId the column to search into
209     */
210    protected A_CmsListDialog(
211        CmsJspActionElement jsp,
212        String listId,
213        CmsMessageContainer listName,
214        String sortedColId,
215        CmsListOrderEnum sortOrder,
216        String searchableColId) {
217
218        this(jsp, listId, listName, sortedColId, sortOrder, searchableColId, false);
219    }
220
221    /**
222     * Public constructor.<p>
223     *
224     * @param jsp an initialized JSP action element
225     * @param listId the id of the displayed list
226     * @param listName the name of the list
227     * @param sortedColId the a priory sorted column
228     * @param sortOrder the order of the sorted column
229     * @param searchableColId the column to search into
230     * @param lazy if this parameter is true, the list should load only load the list items of the current page, if possible
231     */
232    @SuppressWarnings("unchecked")
233    protected A_CmsListDialog(
234        CmsJspActionElement jsp,
235        String listId,
236        CmsMessageContainer listName,
237        String sortedColId,
238        CmsListOrderEnum sortOrder,
239        String searchableColId,
240        boolean lazy) {
241
242        super(jsp);
243        m_lazy = lazy;
244        m_metadatas = (Map<String, CmsListMetadata>)jsp.getRequest().getSession().getAttribute(KEY_META_DATA_CACHE);
245        if (m_metadatas == null) {
246            m_metadatas = new HashMap<String, CmsListMetadata>();
247            jsp.getRequest().getSession().setAttribute(KEY_META_DATA_CACHE, m_metadatas);
248        }
249        if (LOG.isDebugEnabled()) {
250            LOG.debug(Messages.get().getBundle().key(Messages.LOG_START_INIT_LIST_1, listId));
251        }
252        // set list id
253        m_listId = listId;
254        // set active flag for 2 lists dialog
255        m_active = (getListId() + "-form").equals(getParamFormName());
256        setParamFormName(getListId() + "-form");
257        // abort if already forwarded
258        if (isForwarded()) {
259            return;
260        }
261        m_searchColId = searchableColId;
262        // try to read the list from the session
263        listRecovery(listId);
264        // initialization
265        if (getList() == null) {
266            // create the list
267            setList(new CmsHtmlList(listId, listName, getMetadata(this.getClass().getName(), listId)));
268            // set the number of items per page from the user settings
269            getList().setMaxItemsPerPage(getSettings().getUserSettings().getExplorerFileEntries());
270            // sort the list
271            if ((sortedColId != null) && (getList().getMetadata().getColumnDefinition(sortedColId) != null)) {
272                getList().setWp(this);
273                getList().setSortedColumn(sortedColId);
274                if ((sortOrder != null) && (sortOrder == CmsListOrderEnum.ORDER_DESCENDING)) {
275                    getList().setSortedColumn(sortedColId);
276                }
277            }
278            // save the current state of the list
279            listSave();
280        }
281        getList().setWp(this);
282        if (LOG.isDebugEnabled()) {
283            LOG.debug(Messages.get().getBundle().key(Messages.LOG_END_INIT_LIST_1, listId));
284        }
285    }
286
287    /**
288     * Returns the list object for the given list dialog, or <code>null</code>
289     * if no list object has been set.<p>
290     *
291     * @param listDialog the list dialog class
292     * @param settings the wp settings for accessing the session
293     *
294     * @return the list object for this list dialog, or <code>null</code>
295     */
296    public static CmsHtmlList getListObject(Class<?> listDialog, CmsWorkplaceSettings settings) {
297
298        return getListObjectMap(settings).get(listDialog.getName());
299    }
300
301    /**
302     * Returns the (internal use only) map of list objects.<p>
303     *
304     * @param settings the wp settings for accessing the session
305     *
306     * @return the (internal use only) map of list objects
307     */
308    private static Map<String, CmsHtmlList> getListObjectMap(CmsWorkplaceSettings settings) {
309
310        Map<String, CmsHtmlList> objects = CmsCollectionsGenericWrapper.map(settings.getListObject());
311        if (objects == null) {
312            // using hashtable as most efficient version of a synchronized map
313            objects = new Hashtable<String, CmsHtmlList>();
314            settings.setListObject(objects);
315        }
316        return objects;
317    }
318
319    /**
320     * Performs the dialog actions depending on the initialized action.<p>
321     *
322     * @throws JspException if dialog actions fail
323     * @throws IOException in case of errors forwarding to the required result page
324     * @throws ServletException in case of errors forwarding to the required result page
325     */
326    public void actionDialog() throws JspException, ServletException, IOException {
327
328        if (isForwarded()) {
329            return;
330        }
331        if (getAction() == ACTION_CANCEL) {
332            // ACTION: cancel button pressed
333            actionCloseDialog();
334            return;
335        }
336
337        if (LOG.isDebugEnabled()) {
338            LOG.debug(
339                Messages.get().getBundle().key(
340                    Messages.LOG_START_ACTION_LIST_2,
341                    getListId(),
342                    Integer.valueOf(getAction())));
343        }
344        switch (getAction()) {
345            //////////////////// ACTION: default actions
346            case ACTION_LIST_SEARCH:
347            case ACTION_LIST_SORT:
348            case ACTION_LIST_SELECT_PAGE:
349                executeDefaultActions();
350                break;
351
352            //////////////////// ACTION: execute single list action
353            case ACTION_LIST_SINGLE_ACTION:
354                if (getSelectedItem() != null) {
355                    executeListSingleActions();
356                }
357                break;
358
359            //////////////////// ACTION: execute multiple list actions
360            case ACTION_LIST_MULTI_ACTION:
361                executeListMultiActions();
362                break;
363
364            //////////////////// ACTION: execute independent list actions
365            case ACTION_LIST_INDEPENDENT_ACTION:
366                executeListIndepActions();
367                break;
368
369            case ACTION_DEFAULT:
370            default:
371                // ACTION: show dialog (default)
372                setParamAction(DIALOG_INITIAL);
373        }
374        if (LOG.isDebugEnabled()) {
375            LOG.debug(
376                Messages.get().getBundle().key(
377                    Messages.LOG_END_ACTION_LIST_2,
378                    getListId(),
379                    Integer.valueOf(getAction())));
380        }
381        refreshList();
382    }
383
384    /**
385     * Generates the dialog starting html code.<p>
386     *
387     * @return html code
388     */
389    public String defaultActionHtml() {
390
391        if ((getList() != null) && getList().getAllContent().isEmpty()) {
392            // TODO: check the need for this
393            refreshList();
394        }
395        StringBuffer result = new StringBuffer(2048);
396        result.append(defaultActionHtmlStart());
397        result.append(customHtmlStart());
398        result.append(defaultActionHtmlContent());
399        result.append(customHtmlEnd());
400        result.append(defaultActionHtmlEnd());
401        return result.toString();
402    }
403
404    /**
405     * Performs the dialog actions depending on the initialized action and displays the dialog form.<p>
406     *
407     * @throws JspException if dialog actions fail
408     * @throws IOException if writing to the JSP out fails, or in case of errors forwarding to the required result page
409     * @throws ServletException in case of errors forwarding to the required result page
410     */
411    public void displayDialog() throws JspException, IOException, ServletException {
412
413        displayDialog(false);
414    }
415
416    /**
417     * Performs the dialog actions depending on the initialized action and displays the dialog form if needed.<p>
418     *
419     * @param writeLater if <code>true</code> no output is written,
420     *                   you have to call manually the <code>{@link #defaultActionHtml()}</code> method.
421     *
422     * @throws JspException if dialog actions fail
423     * @throws IOException if writing to the JSP out fails, or in case of errors forwarding to the required result page
424     * @throws ServletException in case of errors forwarding to the required result page
425     */
426    public void displayDialog(boolean writeLater) throws JspException, IOException, ServletException {
427
428        actionDialog();
429        if (writeLater) {
430            return;
431        }
432        writeDialog();
433    }
434
435    /**
436     * This method execute the default actions for searching, sorting and paging.<p>
437     */
438    public void executeDefaultActions() {
439
440        switch (getAction()) {
441
442            case ACTION_LIST_SEARCH:
443                executeSearch();
444                break;
445            case ACTION_LIST_SORT:
446                executeSort();
447                break;
448            case ACTION_LIST_SELECT_PAGE:
449                executeSelectPage();
450                break;
451            default:
452                // ignore
453        }
454        listSave();
455    }
456
457    /**
458     * This method should handle the default list independent actions,
459     * by comparing <code>{@link #getParamListAction()}</code> with the id
460     * of the action to execute.<p>
461     *
462     * if you want to handle additional independent actions, override this method,
463     * handling your actions and FINALLY calling <code>super.executeListIndepActions();</code>.<p>
464     */
465    public void executeListIndepActions() {
466
467        if (getList().getMetadata().getItemDetailDefinition(getParamListAction()) != null) {
468            // toggle item details
469            getList().getMetadata().toogleDetailState(getParamListAction());
470            // lazy initialization
471            initializeDetail(getParamListAction());
472        }
473        listSave();
474    }
475
476    /**
477     * This method should handle every defined list multi action,
478     * by comparing <code>{@link #getParamListAction()}</code> with the id
479     * of the action to execute.<p>
480     *
481     * @throws IOException in case of errors when including a required sub-element
482     * @throws ServletException in case of errors when including a required sub-element
483     * @throws CmsRuntimeException to signal that an action is not supported
484     */
485    public abstract void executeListMultiActions() throws IOException, ServletException, CmsRuntimeException;
486
487    /**
488     * This method should handle every defined list single action,
489     * by comparing <code>{@link #getParamListAction()}</code> with the id
490     * of the action to execute.<p>
491     *
492     * @throws IOException in case of errors when including a required sub-element
493     * @throws ServletException in case of errors when including a required sub-element
494     * @throws CmsRuntimeException to signal that an action is not supported
495     */
496    public abstract void executeListSingleActions() throws IOException, ServletException, CmsRuntimeException;
497
498    /**
499     * Returns the list.<p>
500     *
501     * @return the list
502     */
503    public CmsHtmlList getList() {
504
505        if ((m_list != null) && (m_list.getMetadata() == null)) {
506            m_list.setMetadata(getMetadata(getClass().getName(), m_list.getId()));
507        }
508        return m_list;
509    }
510
511    /**
512     * Returns the Id of the list.<p>
513     *
514     * @return the list Id
515     */
516    public final String getListId() {
517
518        return m_listId;
519    }
520
521    /**
522     * Returns the list metadata object for the given dialog.<p>
523     *
524     * @param listDialogName the dialog class name
525     *
526     * @return the list metadata object
527     */
528    public CmsListMetadata getMetadata(String listDialogName) {
529
530        return getMetadataCache().get(listDialogName);
531    }
532
533    /**
534     * Should generate the metadata definition for the list, and return the
535     * corresponding <code>{@link CmsListMetadata}</code> object.<p>
536     *
537     * @param listDialogName the name of the class generating the list
538     * @param listId the id of the list
539     *
540     * @return The metadata for the given list
541     */
542    public synchronized CmsListMetadata getMetadata(String listDialogName, String listId) {
543
544        getSettings();
545        String metaDataKey = listDialogName + listId;
546
547        if ((getMetadataCache().get(metaDataKey) == null) || getMetadataCache().get(metaDataKey).isVolatile()) {
548            if (LOG.isDebugEnabled()) {
549                LOG.debug(Messages.get().getBundle().key(Messages.LOG_START_METADATA_LIST_1, getListId()));
550            }
551            CmsListMetadata metadata = new CmsListMetadata(listId);
552
553            setColumns(metadata);
554            // always check the search action
555            setSearchAction(metadata, m_searchColId);
556            setIndependentActions(metadata);
557            metadata.addIndependentAction(new CmsListPrintIAction());
558            setMultiActions(metadata);
559            metadata.checkIds();
560            getMetadataCache().put(metaDataKey, metadata);
561            if (LOG.isDebugEnabled()) {
562                LOG.debug(Messages.get().getBundle().key(Messages.LOG_END_METADATA_LIST_1, getListId()));
563            }
564        }
565        return getMetadata(metaDataKey);
566    }
567
568    /**
569     * Returns the form name.<p>
570     *
571     * @return the form name
572     */
573    public String getParamFormName() {
574
575        return m_paramFormName;
576    }
577
578    /**
579     * Returns the List Action.<p>
580     *
581     * @return the List Action
582     */
583    public String getParamListAction() {
584
585        return m_paramListAction;
586    }
587
588    /**
589     * Returns the current Page.<p>
590     *
591     * @return the current Page
592     */
593    public String getParamPage() {
594
595        return m_paramPage;
596    }
597
598    /**
599     * Returns the Search Filter.<p>
600     *
601     * @return the Search Filter
602     */
603    public String getParamSearchFilter() {
604
605        return m_paramSearchFilter;
606    }
607
608    /**
609     * Returns the selected Items.<p>
610     *
611     * @return the selected Items
612     */
613    public String getParamSelItems() {
614
615        return m_paramSelItems;
616    }
617
618    /**
619     * Returns the sorted Column.<p>
620     *
621     * @return the sorted Column
622     */
623    public String getParamSortCol() {
624
625        return m_paramSortCol;
626    }
627
628    /**
629     * Returns the current selected item.<p>
630     *
631     * @return the current selected item
632     */
633    public CmsListItem getSelectedItem() {
634
635        try {
636            return getList().getItem(
637                CmsStringUtil.splitAsArray(getParamSelItems(), CmsHtmlList.ITEM_SEPARATOR)[0].trim());
638        } catch (Exception e) {
639            try {
640                return getList().getItem("");
641            } catch (Exception e1) {
642                return null;
643            }
644        }
645    }
646
647    /**
648     * Returns a list of current selected items.<p>
649     *
650     * @return a list of current selected items
651     */
652    public List<CmsListItem> getSelectedItems() {
653
654        Iterator<String> it = CmsStringUtil.splitAsList(
655            getParamSelItems(),
656            CmsHtmlList.ITEM_SEPARATOR,
657            true).iterator();
658        List<CmsListItem> items = new ArrayList<CmsListItem>();
659        while (it.hasNext()) {
660            String id = it.next();
661            items.add(getList().getItem(id));
662        }
663        return items;
664    }
665
666    /**
667     * Returns the activation flag.<p>
668     *
669     * Useful for dialogs with several lists.<p>
670     *
671     * Is <code></code> if the original <code>formname</code> parameter
672     * is equals to <code>${listId}-form</code>.<p>
673     *
674     * @return the activation flag
675     */
676    public boolean isActive() {
677
678        return m_active;
679    }
680
681    /**
682     * This method re-read the rows of the list, the user should call this method after executing an action
683     * that add or remove rows to the list.<p>
684     */
685    public synchronized void refreshList() {
686
687        if (getList() == null) {
688            return;
689        }
690        if (LOG.isDebugEnabled()) {
691            LOG.debug(Messages.get().getBundle().key(Messages.LOG_START_REFRESH_LIST_1, getListId()));
692        }
693        m_listState = getList().getState();
694        getList().clear();
695        fillList();
696        getList().setState(m_listState);
697        m_listState = null;
698        listSave();
699        if (LOG.isDebugEnabled()) {
700            LOG.debug(Messages.get().getBundle().key(Messages.LOG_END_REFRESH_LIST_1, getListId()));
701        }
702    }
703
704    /**
705     * Removes the list from the workplace settings.<p>
706     *
707     * Next time the list is displayed the list will be reloaded.<p>
708     */
709    public void removeList() {
710
711        setList(null);
712        listSave();
713    }
714
715    /**
716     * Sets the list.<p>
717     *
718     * @param list the list to set
719     */
720    public void setList(CmsHtmlList list) {
721
722        m_list = list;
723    }
724
725    /**
726     * Stores the given object as "list object" for the given list dialog in the current users session.<p>
727     *
728     * @param listDialog the list dialog class
729     * @param listObject the list to store
730     */
731    public void setListObject(Class<?> listDialog, CmsHtmlList listObject) {
732
733        if (listObject == null) {
734            // null object: remove the entry from the map
735            getListObjectMap(getSettings()).remove(listDialog.getName());
736        } else {
737            if ((listObject.getMetadata() != null) && listObject.getMetadata().isVolatile()) {
738                listObject.setMetadata(null);
739            }
740            getListObjectMap(getSettings()).put(listDialog.getName(), listObject);
741        }
742    }
743
744    /**
745     * Sets the form name.<p>
746     *
747     * @param formName the form name to set
748     */
749    public void setParamFormName(String formName) {
750
751        m_paramFormName = formName;
752    }
753
754    /**
755     * Sets the List Action.<p>
756     *
757     * @param listAction the list Action to set
758     */
759    public void setParamListAction(String listAction) {
760
761        m_paramListAction = listAction;
762    }
763
764    /**
765     * Sets the current Page.<p>
766     *
767     * @param page the current Page to set
768     */
769    public void setParamPage(String page) {
770
771        m_paramPage = page;
772    }
773
774    /**
775     * Sets the Search Filter.<p>
776     *
777     * @param searchFilter the Search Filter to set
778     */
779    public void setParamSearchFilter(String searchFilter) {
780
781        m_paramSearchFilter = searchFilter;
782    }
783
784    /**
785     * Sets the selected Items.<p>
786     *
787     * @param paramSelItems the selected Items to set
788     */
789    public void setParamSelItems(String paramSelItems) {
790
791        m_paramSelItems = paramSelItems;
792    }
793
794    /**
795     * Sets the sorted Column.<p>
796     *
797     * @param sortCol the sorted Column to set
798     */
799    public void setParamSortCol(String sortCol) {
800
801        m_paramSortCol = sortCol;
802    }
803
804    /**
805     * Writes the dialog html code, only if the <code>{@link #ACTION_DEFAULT}</code> is set.<p>
806     *
807     * @throws IOException if writing to the JSP out fails, or in case of errros forwarding to the required result page
808     */
809    public void writeDialog() throws IOException {
810
811        if (isForwarded()) {
812            return;
813        }
814        if (LOG.isDebugEnabled()) {
815            LOG.debug(Messages.get().getBundle().key(Messages.LOG_START_WRITE_LIST_1, getListId()));
816        }
817        JspWriter out = getJsp().getJspContext().getOut();
818        out.print(defaultActionHtml());
819        if (LOG.isDebugEnabled()) {
820            LOG.debug(Messages.get().getBundle().key(Messages.LOG_END_WRITE_LIST_1, getListId()));
821        }
822    }
823
824    /**
825     * Can be overwritten to add some code after the list.<p>
826     *
827     * @return custom html code
828     */
829    protected String customHtmlEnd() {
830
831        return dialogContentEnd();
832    }
833
834    /**
835     * Can be overwritten to add some code before the list.<p>
836     *
837     * @return custom html code
838     */
839    protected String customHtmlStart() {
840
841        return "";
842    }
843
844    /**
845     * Returns the html code for the default action content.<p>
846     *
847     * @return html code
848     */
849    protected String defaultActionHtmlContent() {
850
851        StringBuffer result = new StringBuffer(2048);
852        result.append("<form name='");
853        result.append(getList().getId());
854        result.append("-form' action='");
855        result.append(getDialogRealUri());
856        result.append("' method='post' class='nomargin'");
857        if (getList().getMetadata().isSearchable()) {
858            result.append(" onsubmit=\"listSearchAction('");
859            result.append(getList().getId());
860            result.append("', '");
861            result.append(getList().getMetadata().getSearchAction().getId());
862            result.append("', '");
863            result.append(getList().getMetadata().getSearchAction().getConfirmationMessage().key(getLocale()));
864            result.append("');\"");
865        }
866        result.append(">\n");
867        result.append(allParamsAsHidden());
868        result.append("\n");
869        getList().setWp(this);
870        result.append(getList().listHtml());
871        result.append("\n</form>\n");
872        return result.toString();
873    }
874
875    /**
876     * Generates the dialog ending html code.<p>
877     *
878     * @return html code
879     */
880    protected String defaultActionHtmlEnd() {
881
882        StringBuffer result = new StringBuffer(2048);
883        result.append(dialogEnd());
884        result.append(bodyEnd());
885        result.append(htmlEnd());
886        return result.toString();
887    }
888
889    /**
890     * Generates the dialog starting html code.<p>
891     *
892     * @return html code
893     */
894    protected String defaultActionHtmlStart() {
895
896        StringBuffer result = new StringBuffer(2048);
897        result.append(htmlStart(null));
898        result.append(getList().listJs());
899        result.append(bodyStart("dialog", null));
900        result.append(dialogStart());
901        result.append(dialogContentStart(getParamTitle()));
902        return result.toString();
903    }
904
905    /**
906     * Filter a list, given the action is set to <code>LIST_SEARCH</code> and
907     * the filter text is set in the <code>PARAM_SEARCH_FILTER</code> parameter.<p>
908     */
909    protected void executeSearch() {
910
911        getList().setSearchFilter(getParamSearchFilter());
912    }
913
914    /**
915     * Select a page, given the action is set to <code>LIST_SELECT_PAGE</code> and
916     * the page to go to is set in the <code>PARAM_PAGE</code> parameter.<p>
917     */
918    protected void executeSelectPage() {
919
920        int page = Integer.valueOf(getParamPage()).intValue();
921        getList().setCurrentPage(page);
922    }
923
924    /**
925     * Sort the list, given the action is set to <code>LIST_SORT</code> and
926     * the sort column is set in the <code>PARAM_SORT_COL</code> parameter.<p>
927     */
928    protected void executeSort() {
929
930        getList().setSortedColumn(getParamSortCol());
931    }
932
933    /**
934     * Lazy initialization for detail data.<p>
935     *
936     * Should fill the given detail column for every list item in <code>{@link CmsHtmlList#getContent()}</code>
937     *
938     * Should not throw any kind of exception.<p>
939     *
940     * @param detailId the id of the detail to initialize
941     */
942    protected abstract void fillDetails(String detailId);
943
944    /**
945     * Calls the <code>{@link #getListItems}</code> method and catches any exception.<p>
946     */
947    protected void fillList() {
948
949        try {
950            getList().setContent(getListItems());
951            // initialize detail columns
952            Iterator<CmsListItemDetails> itDetails = getList().getMetadata().getItemDetailDefinitions().iterator();
953            while (itDetails.hasNext()) {
954                initializeDetail(itDetails.next().getId());
955            }
956        } catch (Exception e) {
957            throw new CmsRuntimeException(
958                Messages.get().container(Messages.ERR_LIST_FILL_1, getList().getName().key(getLocale()), null),
959                e);
960        }
961    }
962
963    /**
964     * Should generate a list with the list items to be displayed.<p>
965     *
966     * @return a list of <code>{@link CmsListItem}</code>s
967     *
968     * @throws CmsException if something goes wrong
969     */
970    protected abstract List<CmsListItem> getListItems() throws CmsException;
971
972    /**
973     * Returns the current list state.<p>
974     *
975     * @return the current list state
976     */
977    protected CmsListState getListState() {
978
979        if (m_listState != null) {
980            // in case of refreshList call
981            return m_listState;
982        }
983        return getList().getState();
984    }
985
986    /**
987     * Gets the list metadata cache.<p>
988     *
989     * @return the list metadata cache
990     */
991    protected Map<String, CmsListMetadata> getMetadataCache() {
992
993        return m_metadatas;
994    }
995
996    /**
997     * Lazy details initialization.<p>
998     *
999     * @param detailId the id of the detail column
1000     */
1001    protected void initializeDetail(String detailId) {
1002
1003        // if detail column visible or printable
1004        CmsListItemDetails details = getList().getMetadata().getItemDetailDefinition(detailId);
1005        if (details.isVisible() || details.isPrintable()) {
1006            // if the list is not empty
1007            if (getList().getTotalSize() > 0) {
1008                // if the detail column has not been previously initialized
1009                if (getList().getAllContent().get(0).get(detailId) == null) {
1010                    if (LOG.isDebugEnabled()) {
1011                        LOG.debug(
1012                            Messages.get().getBundle().key(Messages.LOG_START_DETAILS_LIST_2, getListId(), detailId));
1013                    }
1014                    fillDetails(detailId);
1015                    if (LOG.isDebugEnabled()) {
1016                        LOG.debug(
1017                            Messages.get().getBundle().key(Messages.LOG_END_DETAILS_LIST_2, getListId(), detailId));
1018                    }
1019                }
1020            }
1021        }
1022    }
1023
1024    /**
1025     * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, jakarta.servlet.http.HttpServletRequest)
1026     */
1027    @Override
1028    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
1029
1030        super.initWorkplaceRequestValues(settings, request);
1031        // set the action for the JSP switch
1032        if (LIST_SEARCH.equals(getParamAction())) {
1033            setAction(ACTION_LIST_SEARCH);
1034        } else if (LIST_SORT.equals(getParamAction())) {
1035            setAction(ACTION_LIST_SORT);
1036        } else if (LIST_SELECT_PAGE.equals(getParamAction())) {
1037            setAction(ACTION_LIST_SELECT_PAGE);
1038        } else if (LIST_INDEPENDENT_ACTION.equals(getParamAction())) {
1039            setAction(ACTION_LIST_INDEPENDENT_ACTION);
1040        } else if (LIST_SINGLE_ACTION.equals(getParamAction())) {
1041            setAction(ACTION_LIST_SINGLE_ACTION);
1042        } else if (LIST_MULTI_ACTION.equals(getParamAction())) {
1043            setAction(ACTION_LIST_MULTI_ACTION);
1044        }
1045        setParamStyle(CmsToolDialog.STYLE_NEW);
1046        // test the needed parameters
1047        try {
1048            validateParamaters();
1049        } catch (Exception e) {
1050            // redirect to parent if parameters not available
1051            setAction(ACTION_CANCEL);
1052            try {
1053                actionCloseDialog();
1054            } catch (JspException e1) {
1055                // noop
1056            }
1057            return;
1058        }
1059    }
1060
1061    /**
1062     * Recover the last list instance that is read from the request attributes.<p>
1063     *
1064     * This is required for keep the whole list in memory while you browse a page.<p>
1065     *
1066     * @param listId the id of the expected list
1067     */
1068    protected synchronized void listRecovery(String listId) {
1069
1070        CmsHtmlList list = getListObject(this.getClass(), getSettings());
1071        if ((list != null) && !list.getId().equals(listId)) {
1072            list = null;
1073        }
1074        setList(list);
1075    }
1076
1077    /**
1078     * Save the state of the list in the session.<p>
1079     */
1080    protected synchronized void listSave() {
1081
1082        setListObject(this.getClass(), getList());
1083    }
1084
1085    /**
1086     * Should create the columns and add them to the given list metadata object.<p>
1087     *
1088     * This method will be just executed once, the first time the constructor is called.<p>
1089     *
1090     * @param metadata the list metadata
1091     */
1092    protected abstract void setColumns(CmsListMetadata metadata);
1093
1094    /**
1095     * Should add the independent actions to the given list metadata object.<p>
1096     *
1097     * This method will be just executed once, the first time the constructor is called.<p>
1098     *
1099     * @param metadata the list metadata
1100     */
1101    protected abstract void setIndependentActions(CmsListMetadata metadata);
1102
1103    /**
1104     * Should add the multi actions to the given list metadata object.<p>
1105     *
1106     * This method will be just executed once, the first time the constructor is called.<p>
1107     *
1108     * @param metadata the list metadata
1109     */
1110    protected abstract void setMultiActions(CmsListMetadata metadata);
1111
1112    /**
1113     * Creates the default search action.<p>
1114     *
1115     * Can be overridden for more sophisticated search.<p>
1116     *
1117     * @param metadata the metadata of the list to do searchable
1118     * @param columnId the if of the column to search into
1119     */
1120    protected void setSearchAction(CmsListMetadata metadata, String columnId) {
1121
1122        CmsListColumnDefinition col = metadata.getColumnDefinition(columnId);
1123        if ((columnId != null) && (col != null)) {
1124            if (metadata.getSearchAction() == null) {
1125                // makes the list searchable
1126                CmsListSearchAction searchAction = new CmsListSearchAction(col);
1127                searchAction.useDefaultShowAllAction();
1128                metadata.setSearchAction(searchAction);
1129            }
1130        }
1131    }
1132
1133    /**
1134     * A convenient method to throw a list unsupported
1135     * action runtime exception.<p>
1136     *
1137     * Should be triggered if your list implementation does not
1138     * support the <code>{@link #getParamListAction()}</code>
1139     * action.<p>
1140     *
1141     * @throws CmsRuntimeException always to signal that this operation is not supported
1142     */
1143    protected void throwListUnsupportedActionException() throws CmsRuntimeException {
1144
1145        throw new CmsRuntimeException(
1146            Messages.get().container(
1147                Messages.ERR_LIST_UNSUPPORTED_ACTION_2,
1148                getList().getName().key(getLocale()),
1149                getParamListAction()));
1150    }
1151
1152    /**
1153     * Should be overridden for parameter validation.<p>
1154     *
1155     * @throws Exception if the parameters are not valid
1156     */
1157    protected void validateParamaters() throws Exception {
1158
1159        // valid by default
1160    }
1161}