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, 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.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        /**Trace level.*/
188        Trace(Level.TRACE, OpenCmsTheme.TABLE_COLUMN_BOX_RED, null),
189
190        /**Debug level.*/
191        Debug(Level.DEBUG, OpenCmsTheme.TABLE_COLUMN_BOX_RED, null),
192
193        /**Error level. */
194        Error(Level.ERROR, OpenCmsTheme.TABLE_COLUMN_BOX_CYAN, "Default"),
195
196        /**Fatal level.*/
197        Fatal(Level.FATAL, OpenCmsTheme.TABLE_COLUMN_BOX_BLUE_LIGHT, null),
198
199        /**Info level. */
200        Info(Level.INFO, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE_DARK, null),
201
202        /**Off level. */
203        Off(Level.OFF, OpenCmsTheme.TABLE_COLUMN_BOX_GRAY, null),
204
205        /**Warning level. */
206        Warn(Level.WARN, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE, null);
207
208        /**Caption for logger level.*/
209        private String m_caption;
210
211        /**CSS class.*/
212        private String m_css;
213
214        /**Corresponging log4j Level.*/
215        private Level m_level;
216
217        /**
218         * constructor.<p>
219         *
220         * @param level of logger
221         * @param css class
222         * @param caption for the level
223         */
224        private LoggerLevel(Level level, String css, String caption) {
225
226            m_css = css;
227            m_level = level;
228            m_caption = caption;
229        }
230
231        /**
232         * Returns LoggerLevel object from given logger.<p>
233         *
234         * @param logger to fing enumeration object for
235         * @return LoggerLevel
236         */
237        protected static LoggerLevel fromLogger(Logger logger) {
238
239            for (LoggerLevel item : LoggerLevel.values()) {
240                if (item.getLevel().equals(logger.getLevel())) {
241                    return item;
242                }
243            }
244            return null;
245        }
246
247        /**
248         * Returns path to icon.<p>
249         *
250         * @return path to icon
251         */
252        protected String getCssClass() {
253
254            return m_css;
255        }
256
257        /**
258         * Returns level. <p>
259         * @return log4j Level
260         */
261        protected Level getLevel() {
262
263            return m_level;
264        }
265
266        /**
267         * Returns the string representation for level.<p>
268         *
269         * @return string
270         */
271        protected String getLevelString() {
272
273            if (m_caption == null) {
274                String out = m_level.toString();
275                return out.substring(0, 1).toUpperCase() + out.substring(1).toLowerCase();
276            }
277            return m_caption;
278        }
279
280        /**
281         * Returns an extenden string representation with log level name added in case of having caption set.<p>
282         *
283         * @return string
284         */
285        protected String getLevelStringComplet() {
286
287            if (m_caption == null) {
288                return getLevelString();
289            }
290            String level = m_level.toString();
291            level = level.substring(0, 1).toUpperCase() + level.substring(1).toLowerCase();
292            return m_caption + " (" + level + ")";
293        }
294
295    }
296
297    /** Channel name for logging logger configuration changes. */
298    public static final String LOGCHANGES_NAME = "logchanges";
299
300    /** Logger for logging logger configuration changes. */
301    private static final Log LOGCHANGES = CmsLog.getLog(LOGCHANGES_NAME);
302
303    /**vaadin serial id.*/
304    private static final long serialVersionUID = 5467369614234190999L;
305
306    /**Container holding table data. */
307    private IndexedContainer m_container;
308
309    /**Context menu. */
310    private CmsContextMenu m_menu;
311
312    /**Instance of the app */
313    private CmsLogFileApp m_app;
314
315    /**
316     * constructor.<p>
317     * @param app the app instance
318     */
319    protected CmsLogChannelTable(CmsLogFileApp app) {
320
321        m_app = app;
322        m_container = new IndexedContainer();
323
324        setContainerDataSource(m_container);
325
326        for (TableColumn prop : TableColumn.values()) {
327            m_container.addContainerProperty(prop, prop.getType(), prop.getDefaultValue());
328            setColumnHeader(prop, prop.getLocalizedMessage());
329        }
330
331        setVisibleColumns(TableColumn.Level, TableColumn.Channel, TableColumn.ParentChannel, TableColumn.File);
332
333        setItemIconPropertyId(TableColumn.Icon);
334        setColumnWidth(null, 40);
335        setRowHeaderMode(RowHeaderMode.ICON_ONLY);
336
337        setColumnWidth(TableColumn.Level, 80);
338
339        setSelectable(true);
340        setMultiSelect(true);
341        m_menu = new CmsContextMenu();
342        m_menu.setAsTableContextMenu(this);
343        addItemClickListener(new ItemClickListener() {
344
345            private static final long serialVersionUID = 1L;
346
347            public void itemClick(ItemClickEvent event) {
348
349                onItemClick(event, event.getItemId(), event.getPropertyId());
350            }
351        });
352        setCellStyleGenerator(new CellStyleGenerator() {
353
354            private static final long serialVersionUID = 1L;
355
356            public String getStyle(Table source, Object itemId, Object propertyId) {
357
358                if (TableColumn.Channel.equals(propertyId)) {
359                    return " " + OpenCmsTheme.HOVER_COLUMN;
360                }
361
362                if (TableColumn.Level.equals(propertyId)) {
363                    return ((LoggerLevel)source.getItem(itemId).getItemProperty(propertyId).getValue()).getCssClass();
364                }
365
366                return null;
367            }
368        });
369
370        addGeneratedColumn(TableColumn.Level, new LevelIcon());
371
372        fillTable();
373    }
374
375    /**
376     * Adds a container item for the given logger.<p>
377     *
378     * @param logger the logger for which to generate a container item
379     */
380    public void addItemForLogger(Logger logger) {
381
382        Item item = m_container.addItem(logger);
383        if (item != null) {
384            item.getItemProperty(TableColumn.Channel).setValue(logger.getName());
385            String parentChannelName = getParentLogChannelName(logger);
386            item.getItemProperty(TableColumn.ParentChannel).setValue(
387                parentChannelName != null ? parentChannelName : "none");
388            item.getItemProperty(TableColumn.File).setValue(m_app.getLogFile(logger));
389            item.getItemProperty(TableColumn.Level).setValue(LoggerLevel.fromLogger(logger));
390        }
391    }
392
393    /**
394     * Filters the table according to given search string.<p>
395     *
396     * @param search string to be looked for.
397     */
398    @SuppressWarnings("unchecked")
399    public void filterTable(String search) {
400
401        m_container.removeAllContainerFilters();
402        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) {
403            m_container.addContainerFilter(
404                new Or(
405                    new SimpleStringFilter(TableColumn.Channel, search, true, false),
406                    new SimpleStringFilter(TableColumn.ParentChannel, search, true, false),
407                    new SimpleStringFilter(TableColumn.File, search, true, false)));
408        }
409        if ((getValue() != null) & !((Set<Logger>)getValue()).isEmpty()) {
410            setCurrentPageFirstItemId(((Set<Logger>)getValue()).iterator().next());
411        }
412    }
413
414    /**
415     * Toggles the log file of a given log channel.<p>
416     *
417     * @param logchannel to set or remove log file to
418     */
419    protected void toggleOwnFile(Logger logchannel) {
420
421        m_app.toggleOwnFileForLogger(logchannel);
422        m_app.updateTable();
423    }
424
425    /**
426     * Sets a given Level to a Set of Loggers.<p>
427     *
428     * @param clickedLevel to be set
429     * @param clickedLogger to get level changed
430     */
431    void changeLoggerLevel(LoggerLevel clickedLevel, Set<Logger> clickedLogger) {
432
433        for (Logger logger : clickedLogger) {
434
435            logLogLevelChange(logger, clickedLevel.getLevel());
436            logger.setLevel(clickedLevel.getLevel());
437            m_app.writeElement(logger);
438        }
439        m_app.updateTable();
440    }
441
442    /**
443     * Handles the table item clicks, including clicks on images inside of a table item.<p>
444     *
445     * @param event the click event
446     * @param itemId of the clicked row
447     * @param propertyId column id
448     */
449    @SuppressWarnings("unchecked")
450    void onItemClick(MouseEvents.ClickEvent event, Object itemId, Object propertyId) {
451
452        if (!event.isCtrlKey() && !event.isShiftKey()) {
453
454            if (event.getButton().equals(MouseButton.LEFT)) {
455                setValue(null);
456            }
457            changeValueIfNotMultiSelect(itemId);
458            // don't interfere with multi-selection using control key
459            if (event.getButton().equals(MouseButton.RIGHT) || (propertyId == null)) {
460                m_menu.removeAllItems();
461                fillContextMenu((Set<Logger>)getValue());
462                m_menu.openForTable(event, itemId, propertyId, this);
463            }
464
465        }
466    }
467
468    /**
469     * Checks value of table and sets it new if needed:<p>
470     * if multiselect: new itemId is in current Value? -> no change of value<p>
471     * no multiselect and multiselect, but new item not selected before: set value to new item<p>
472     *
473     * @param itemId if of clicked item
474     */
475    private void changeValueIfNotMultiSelect(Object itemId) {
476
477        @SuppressWarnings("unchecked")
478        Set<String> value = (Set<String>)getValue();
479        if (value == null) {
480            select(itemId);
481        } else if (!value.contains(itemId)) {
482            setValue(null);
483            select(itemId);
484        }
485    }
486
487    /**
488     * Fills the context menu.<p>
489     *
490     * @param loggerSet Set of logger to open context menu for
491     */
492    private void fillContextMenu(final Set<Logger> loggerSet) {
493
494        for (LoggerLevel level : LoggerLevel.values()) {
495            final LoggerLevel currentLevel = level;
496            ContextMenuItem item = m_menu.addItem(level.getLevelStringComplet());
497            item.setData(loggerSet);
498            item.addItemClickListener(new ContextMenuItemClickListener() {
499
500                public void contextMenuItemClicked(ContextMenuItemClickEvent event) {
501
502                    changeLoggerLevel(currentLevel, loggerSet);
503
504                }
505            });
506            if (loggerSet.size() == 1) {
507                if (level.getLevel().equals(loggerSet.iterator().next().getLevel())) {
508                    item.setIcon(FontOpenCms.CHECK_SMALL);
509                }
510            }
511        }
512        if (loggerSet.size() == 1) {
513            String message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_NEWFILE_0);
514            if (CmsLogFileApp.isloggingactivated(loggerSet.iterator().next())) {
515                message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_REMOVEFILE_0);
516            }
517            ContextMenuItem item = m_menu.addItem(message);
518            item.setData(loggerSet);
519            item.addItemClickListener(new ContextMenuItemClickListener() {
520
521                public void contextMenuItemClicked(ContextMenuItemClickEvent event) {
522
523                    toggleOwnFile(loggerSet.iterator().next());
524
525                }
526            });
527        }
528    }
529
530    /**
531     * Populate table.<p>
532     */
533    private void fillTable() {
534
535        removeAllItems();
536        for (Logger logger : m_app.getAllElements()) {
537            addItemForLogger(logger);
538        }
539    }
540
541    /**
542     * Gets the parent log channel name of a logger.
543     *
544     * @param logger the logger
545     * @return the parent log channel name
546     */
547    private String getParentLogChannelName(Logger logger) {
548
549        LoggerConfig parentConfig = null;
550        if (logger.getName().equals(logger.get().getName())) {
551            parentConfig = logger.get().getParent();
552        } else {
553            parentConfig = logger.get();
554        }
555        return parentConfig != null ? parentConfig.getName() : null;
556    }
557
558    /**
559     * Helper method for logging user actions.
560     *
561     * @param message the message to write
562     */
563    private void log(String message) {
564
565        String user = A_CmsUI.getCmsObject().getRequestContext().getCurrentUser().getName();
566        LOGCHANGES.info("[User: " + user + "] " + message);
567    }
568
569    /**
570     * Logs a log level change.
571     *
572     * @param logger the logger
573     * @param newLevel the new level to be set on the logger
574     */
575    private void logLogLevelChange(Logger logger, Level newLevel) {
576
577        String oldLevelDesc = null;
578        LoggerConfig config = logger.get();
579        if (logger.getName().equals(config.getName())) {
580            oldLevelDesc = config.getLevel().toString();
581        } else {
582            oldLevelDesc = config.getLevel().toString() + " (inherited from '" + config.getName() + "')";
583        }
584        log("Switching channel '" + logger.getName() + "' from " + oldLevelDesc + " to " + newLevel.toString());
585    }
586
587}