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}