001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.apps.logfile;
029
030import org.opencms.main.CmsLog;
031import org.opencms.ui.A_CmsUI;
032import org.opencms.ui.CmsCssIcon;
033import org.opencms.ui.CmsVaadinUtils;
034import org.opencms.ui.FontOpenCms;
035import org.opencms.ui.apps.Messages;
036import org.opencms.ui.components.OpenCmsTheme;
037import org.opencms.ui.contextmenu.CmsContextMenu;
038import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItem;
039import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickEvent;
040import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickListener;
041import org.opencms.util.CmsStringUtil;
042
043import java.util.ArrayList;
044import java.util.List;
045import java.util.Set;
046
047import org.apache.commons.logging.Log;
048import org.apache.logging.log4j.Level;
049import org.apache.logging.log4j.core.Logger;
050import org.apache.logging.log4j.core.config.LoggerConfig;
051
052import com.vaadin.event.MouseEvents;
053import com.vaadin.server.Resource;
054import com.vaadin.shared.MouseEventDetails.MouseButton;
055import com.vaadin.v7.data.Item;
056import com.vaadin.v7.data.util.IndexedContainer;
057import com.vaadin.v7.data.util.filter.Or;
058import com.vaadin.v7.data.util.filter.SimpleStringFilter;
059import com.vaadin.v7.event.ItemClickEvent;
060import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
061import com.vaadin.v7.ui.Table;
062
063/**
064 * Class for table to display and edit Log channels.<p>
065 */
066@SuppressWarnings("deprecation")
067public class CmsLogChannelTable extends Table {
068
069    /**
070     * Table column generator for Level-buttons.<p>
071     * */
072    class LevelIcon implements Table.ColumnGenerator {
073
074        /**vaadin serial id. */
075        private static final long serialVersionUID = 7258796583481183276L;
076
077        /**
078         * @see com.vaadin.ui.Table.ColumnGenerator#generateCell(com.vaadin.ui.Table, java.lang.Object, java.lang.Object)
079         */
080        public Object generateCell(Table source, final Object itemId, Object columnId) {
081
082            return ((LoggerLevel)(source.getItem(itemId).getItemProperty(columnId).getValue())).getLevelString();
083        }
084
085    }
086
087    /**
088     * Enumeration of Table Columns.<p>
089     */
090    enum TableColumn {
091
092        /**Channel column.*/
093        Channel(Messages.GUI_LOGFILE_LOGSETTINGS_CHANNEL_0, String.class, ""),
094
095        /**Log file column. */
096        File(Messages.GUI_LOGFILE_LOGSETTINGS_FILE_0, String.class, ""),
097
098        /**Icon column.*/
099        Icon(null, Resource.class, new CmsCssIcon(OpenCmsTheme.ICON_LOG)),
100
101        /**Level column.*/
102        Level(Messages.GUI_LOGFILE_LOGSETTINGS_LEVEL_0, LoggerLevel.class, null),
103
104        /**Parent channel column.*/
105        ParentChannel(Messages.GUI_LOGFILE_LOGSETTINGS_PARENT_CHANNEL_0, String.class, "");
106
107        /**Default value for column.*/
108        private Object m_defaultValue;
109
110        /**Header Message key.*/
111        private String m_headerMessage;
112
113        /**Type of column property.*/
114        private Class<?> m_type;
115
116        /**
117         * constructor.
118         *
119         * @param headerMessage key
120         * @param type to property
121         * @param defaultValue of column
122         */
123        TableColumn(String headerMessage, Class<?> type, Object defaultValue) {
124
125            m_headerMessage = headerMessage;
126            m_type = type;
127            m_defaultValue = defaultValue;
128        }
129
130        /**
131         * Returns list of all properties with non-empty header.<p>
132         *
133         * @return list of properties
134         */
135        static List<TableColumn> withHeader() {
136
137            List<TableColumn> props = new ArrayList<TableColumn>();
138
139            for (TableColumn prop : TableColumn.values()) {
140                if (prop.m_headerMessage != null) {
141                    props.add(prop);
142                }
143            }
144            return props;
145        }
146
147        /**
148         * Returns the default value of property.<p>
149         *
150         * @return object
151         */
152        Object getDefaultValue() {
153
154            return m_defaultValue;
155        }
156
157        /**
158         * Returns localized header.<p>
159         *
160         * @return string for header
161         */
162        String getLocalizedMessage() {
163
164            if (m_headerMessage == null) {
165                return "";
166            }
167            return CmsVaadinUtils.getMessageText(m_headerMessage);
168        }
169
170        /**
171         * Returns tye of value for given property.<p>
172         *
173         * @return type
174         */
175        Class<?> getType() {
176
177            return m_type;
178        }
179
180    }
181
182    /**
183     * Enumeration of Logger Level and corresponging icon paths.<p>
184     */
185    private enum LoggerLevel {
186
187        /**Debug level.*/
188        Debug(Level.DEBUG, OpenCmsTheme.TABLE_COLUMN_BOX_RED, null),
189
190        /**Error level. */
191        Error(Level.ERROR, OpenCmsTheme.TABLE_COLUMN_BOX_CYAN, "Default"),
192
193        /**Fatal level.*/
194        Fatal(Level.FATAL, OpenCmsTheme.TABLE_COLUMN_BOX_BLUE_LIGHT, null),
195
196        /**Info level. */
197        Info(Level.INFO, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE_DARK, null),
198
199        /**Off level. */
200        Off(Level.OFF, OpenCmsTheme.TABLE_COLUMN_BOX_GRAY, null),
201
202        /**Warning level. */
203        Warn(Level.WARN, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE, null);
204
205        /**Caption for logger level.*/
206        private String m_caption;
207
208        /**CSS class.*/
209        private String m_css;
210
211        /**Corresponging log4j Level.*/
212        private Level m_level;
213
214        /**
215         * constructor.<p>
216         *
217         * @param level of logger
218         * @param css class
219         * @param caption for the level
220         */
221        private LoggerLevel(Level level, String css, String caption) {
222
223            m_css = css;
224            m_level = level;
225            m_caption = caption;
226        }
227
228        /**
229         * Returns LoggerLevel object from given logger.<p>
230         *
231         * @param logger to fing enumeration object for
232         * @return LoggerLevel
233         */
234        protected static LoggerLevel fromLogger(Logger logger) {
235
236            for (LoggerLevel item : LoggerLevel.values()) {
237                if (item.getLevel().equals(logger.getLevel())) {
238                    return item;
239                }
240            }
241            return null;
242        }
243
244        /**
245         * Returns path to icon.<p>
246         *
247         * @return path to icon
248         */
249        protected String getCssClass() {
250
251            return m_css;
252        }
253
254        /**
255         * Returns level. <p>
256         * @return log4j Level
257         */
258        protected Level getLevel() {
259
260            return m_level;
261        }
262
263        /**
264         * Returns the string representation for level.<p>
265         *
266         * @return string
267         */
268        protected String getLevelString() {
269
270            if (m_caption == null) {
271                String out = m_level.toString();
272                return out.substring(0, 1).toUpperCase() + out.substring(1).toLowerCase();
273            }
274            return m_caption;
275        }
276
277        /**
278         * Returns an extenden string representation with log level name added in case of having caption set.<p>
279         *
280         * @return string
281         */
282        protected String getLevelStringComplet() {
283
284            if (m_caption == null) {
285                return getLevelString();
286            }
287            String level = m_level.toString();
288            level = level.substring(0, 1).toUpperCase() + level.substring(1).toLowerCase();
289            return m_caption + " (" + level + ")";
290        }
291
292    }
293
294    /** Channel name for logging logger configuration changes. */
295    public static final String LOGCHANGES_NAME = "logchanges";
296
297    /** Logger for logging logger configuration changes. */
298    private static final Log LOGCHANGES = CmsLog.getLog(LOGCHANGES_NAME);
299
300    /**vaadin serial id.*/
301    private static final long serialVersionUID = 5467369614234190999L;
302
303    /**Container holding table data. */
304    private IndexedContainer m_container;
305
306    /**Context menu. */
307    private CmsContextMenu m_menu;
308
309    /**Instance of the app */
310    private CmsLogFileApp m_app;
311
312    /**
313     * constructor.<p>
314     * @param app the app instance
315     */
316    protected CmsLogChannelTable(CmsLogFileApp app) {
317
318        m_app = app;
319        m_container = new IndexedContainer();
320
321        setContainerDataSource(m_container);
322
323        for (TableColumn prop : TableColumn.values()) {
324            m_container.addContainerProperty(prop, prop.getType(), prop.getDefaultValue());
325            setColumnHeader(prop, prop.getLocalizedMessage());
326        }
327
328        setVisibleColumns(TableColumn.Level, TableColumn.Channel, TableColumn.ParentChannel, TableColumn.File);
329
330        setItemIconPropertyId(TableColumn.Icon);
331        setColumnWidth(null, 40);
332        setRowHeaderMode(RowHeaderMode.ICON_ONLY);
333
334        setColumnWidth(TableColumn.Level, 80);
335
336        setSelectable(true);
337        setMultiSelect(true);
338        m_menu = new CmsContextMenu();
339        m_menu.setAsTableContextMenu(this);
340        addItemClickListener(new ItemClickListener() {
341
342            private static final long serialVersionUID = 1L;
343
344            public void itemClick(ItemClickEvent event) {
345
346                onItemClick(event, event.getItemId(), event.getPropertyId());
347            }
348        });
349        setCellStyleGenerator(new CellStyleGenerator() {
350
351            private static final long serialVersionUID = 1L;
352
353            public String getStyle(Table source, Object itemId, Object propertyId) {
354
355                if (TableColumn.Channel.equals(propertyId)) {
356                    return " " + OpenCmsTheme.HOVER_COLUMN;
357                }
358
359                if (TableColumn.Level.equals(propertyId)) {
360                    return ((LoggerLevel)source.getItem(itemId).getItemProperty(propertyId).getValue()).getCssClass();
361                }
362
363                return null;
364            }
365        });
366
367        addGeneratedColumn(TableColumn.Level, new LevelIcon());
368
369        fillTable();
370    }
371
372    /**
373     * Adds a container item for the given logger.<p>
374     *
375     * @param logger the logger for which to generate a container item
376     */
377    public void addItemForLogger(Logger logger) {
378
379        Item item = m_container.addItem(logger);
380        if (item != null) {
381            item.getItemProperty(TableColumn.Channel).setValue(logger.getName());
382            String parentChannelName = getParentLogChannelName(logger);
383            item.getItemProperty(TableColumn.ParentChannel).setValue(
384                parentChannelName != null ? parentChannelName : "none");
385            item.getItemProperty(TableColumn.File).setValue(m_app.getLogFile(logger));
386            item.getItemProperty(TableColumn.Level).setValue(LoggerLevel.fromLogger(logger));
387        }
388    }
389
390    /**
391     * Filters the table according to given search string.<p>
392     *
393     * @param search string to be looked for.
394     */
395    @SuppressWarnings("unchecked")
396    public void filterTable(String search) {
397
398        m_container.removeAllContainerFilters();
399        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) {
400            m_container.addContainerFilter(
401                new Or(
402                    new SimpleStringFilter(TableColumn.Channel, search, true, false),
403                    new SimpleStringFilter(TableColumn.ParentChannel, search, true, false),
404                    new SimpleStringFilter(TableColumn.File, search, true, false)));
405        }
406        if ((getValue() != null) & !((Set<Logger>)getValue()).isEmpty()) {
407            setCurrentPageFirstItemId(((Set<Logger>)getValue()).iterator().next());
408        }
409    }
410
411    /**
412     * Toggles the log file of a given log channel.<p>
413     *
414     * @param logchannel to set or remove log file to
415     */
416    protected void toggleOwnFile(Logger logchannel) {
417
418        m_app.toggleOwnFileForLogger(logchannel);
419        m_app.updateTable();
420    }
421
422    /**
423     * Sets a given Level to a Set of Loggers.<p>
424     *
425     * @param clickedLevel to be set
426     * @param clickedLogger to get level changed
427     */
428    void changeLoggerLevel(LoggerLevel clickedLevel, Set<Logger> clickedLogger) {
429
430        for (Logger logger : clickedLogger) {
431
432            logLogLevelChange(logger, clickedLevel.getLevel());
433            logger.setLevel(clickedLevel.getLevel());
434            m_app.writeElement(logger);
435        }
436        m_app.updateTable();
437    }
438
439    /**
440     * Handles the table item clicks, including clicks on images inside of a table item.<p>
441     *
442     * @param event the click event
443     * @param itemId of the clicked row
444     * @param propertyId column id
445     */
446    @SuppressWarnings("unchecked")
447    void onItemClick(MouseEvents.ClickEvent event, Object itemId, Object propertyId) {
448
449        if (!event.isCtrlKey() && !event.isShiftKey()) {
450
451            if (event.getButton().equals(MouseButton.LEFT)) {
452                setValue(null);
453            }
454            changeValueIfNotMultiSelect(itemId);
455            // don't interfere with multi-selection using control key
456            if (event.getButton().equals(MouseButton.RIGHT) || (propertyId == null)) {
457                m_menu.removeAllItems();
458                fillContextMenu((Set<Logger>)getValue());
459                m_menu.openForTable(event, itemId, propertyId, this);
460            }
461
462        }
463    }
464
465    /**
466     * Checks value of table and sets it new if needed:<p>
467     * if multiselect: new itemId is in current Value? -> no change of value<p>
468     * no multiselect and multiselect, but new item not selected before: set value to new item<p>
469     *
470     * @param itemId if of clicked item
471     */
472    private void changeValueIfNotMultiSelect(Object itemId) {
473
474        @SuppressWarnings("unchecked")
475        Set<String> value = (Set<String>)getValue();
476        if (value == null) {
477            select(itemId);
478        } else if (!value.contains(itemId)) {
479            setValue(null);
480            select(itemId);
481        }
482    }
483
484    /**
485     * Fills the context menu.<p>
486     *
487     * @param loggerSet Set of logger to open context menu for
488     */
489    private void fillContextMenu(final Set<Logger> loggerSet) {
490
491        for (LoggerLevel level : LoggerLevel.values()) {
492            final LoggerLevel currentLevel = level;
493            ContextMenuItem item = m_menu.addItem(level.getLevelStringComplet());
494            item.setData(loggerSet);
495            item.addItemClickListener(new ContextMenuItemClickListener() {
496
497                public void contextMenuItemClicked(ContextMenuItemClickEvent event) {
498
499                    changeLoggerLevel(currentLevel, loggerSet);
500
501                }
502            });
503            if (loggerSet.size() == 1) {
504                if (level.getLevel().equals(loggerSet.iterator().next().getLevel())) {
505                    item.setIcon(FontOpenCms.CHECK_SMALL);
506                }
507            }
508        }
509        if (loggerSet.size() == 1) {
510            String message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_NEWFILE_0);
511            if (CmsLogFileApp.isloggingactivated(loggerSet.iterator().next())) {
512                message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_REMOVEFILE_0);
513            }
514            ContextMenuItem item = m_menu.addItem(message);
515            item.setData(loggerSet);
516            item.addItemClickListener(new ContextMenuItemClickListener() {
517
518                public void contextMenuItemClicked(ContextMenuItemClickEvent event) {
519
520                    toggleOwnFile(loggerSet.iterator().next());
521
522                }
523            });
524        }
525    }
526
527    /**
528     * Populate table.<p>
529     */
530    private void fillTable() {
531
532        removeAllItems();
533        for (Logger logger : m_app.getAllElements()) {
534            addItemForLogger(logger);
535        }
536    }
537
538    /**
539     * Gets the parent log channel name of a logger.
540     *
541     * @param logger the logger
542     * @return the parent log channel name
543     */
544    private String getParentLogChannelName(Logger logger) {
545
546        LoggerConfig parentConfig = null;
547        if (logger.getName().equals(logger.get().getName())) {
548            parentConfig = logger.get().getParent();
549        } else {
550            parentConfig = logger.get();
551        }
552        return parentConfig != null ? parentConfig.getName() : null;
553    }
554
555    /**
556     * Helper method for logging user actions.
557     *
558     * @param message the message to write
559     */
560    private void log(String message) {
561
562        String user = A_CmsUI.getCmsObject().getRequestContext().getCurrentUser().getName();
563        LOGCHANGES.info("[User: " + user + "] " + message);
564    }
565
566    /**
567     * Logs a log level change.
568     *
569     * @param logger the logger
570     * @param newLevel the new level to be set on the logger
571     */
572    private void logLogLevelChange(Logger logger, Level newLevel) {
573
574        String oldLevelDesc = null;
575        LoggerConfig config = logger.get();
576        if (logger.getName().equals(config.getName())) {
577            oldLevelDesc = config.getLevel().toString();
578        } else {
579            oldLevelDesc = config.getLevel().toString() + " (inherited from '" + config.getName() + "')";
580        }
581        log("Switching channel '" + logger.getName() + "' from " + oldLevelDesc + " to " + newLevel.toString());
582    }
583
584}