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.ui.CmsCssIcon; 031import org.opencms.ui.CmsVaadinUtils; 032import org.opencms.ui.FontOpenCms; 033import org.opencms.ui.apps.Messages; 034import org.opencms.ui.components.OpenCmsTheme; 035import org.opencms.ui.contextmenu.CmsContextMenu; 036import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItem; 037import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickEvent; 038import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickListener; 039import org.opencms.util.CmsLog4jUtil; 040import org.opencms.util.CmsStringUtil; 041 042import java.io.File; 043import java.util.ArrayList; 044import java.util.List; 045import java.util.Set; 046 047import org.apache.logging.log4j.Level; 048import org.apache.logging.log4j.LogManager; 049import org.apache.logging.log4j.core.Appender; 050import org.apache.logging.log4j.core.Layout; 051import org.apache.logging.log4j.core.Logger; 052import org.apache.logging.log4j.core.LoggerContext; 053import org.apache.logging.log4j.core.appender.FileAppender; 054import org.apache.logging.log4j.core.appender.FileAppender.Builder; 055import org.apache.logging.log4j.core.config.Configuration; 056import org.apache.logging.log4j.core.config.LoggerConfig; 057 058import com.vaadin.event.MouseEvents; 059import com.vaadin.server.Resource; 060import com.vaadin.shared.MouseEventDetails.MouseButton; 061import com.vaadin.v7.data.Item; 062import com.vaadin.v7.data.util.IndexedContainer; 063import com.vaadin.v7.data.util.filter.Or; 064import com.vaadin.v7.data.util.filter.SimpleStringFilter; 065import com.vaadin.v7.event.ItemClickEvent; 066import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; 067import com.vaadin.v7.ui.Table; 068 069/** 070 * Class for table to display and edit Log channels.<p> 071 */ 072public class CmsLogChannelTable extends Table { 073 074 /** 075 * Table column generator for Level-buttons.<p> 076 * */ 077 class LevelIcon implements Table.ColumnGenerator { 078 079 /**vaadin serial id. */ 080 private static final long serialVersionUID = 7258796583481183276L; 081 082 /** 083 * @see com.vaadin.ui.Table.ColumnGenerator#generateCell(com.vaadin.ui.Table, java.lang.Object, java.lang.Object) 084 */ 085 public Object generateCell(Table source, final Object itemId, Object columnId) { 086 087 return ((LoggerLevel)(source.getItem(itemId).getItemProperty(columnId).getValue())).getLevelString(); 088 } 089 090 } 091 092 /** 093 * Enumeration of Table Columns.<p> 094 */ 095 enum TableColumn { 096 097 /**Channel column.*/ 098 Channel(Messages.GUI_LOGFILE_LOGSETTINGS_CHANNEL_0, String.class, ""), 099 100 /**Log file column. */ 101 File(Messages.GUI_LOGFILE_LOGSETTINGS_FILE_0, String.class, ""), 102 103 /**Icon column.*/ 104 Icon(null, Resource.class, new CmsCssIcon(OpenCmsTheme.ICON_LOG)), 105 106 /**Level column.*/ 107 Level(Messages.GUI_LOGFILE_LOGSETTINGS_LEVEL_0, LoggerLevel.class, null), 108 109 /**Parent channel column.*/ 110 ParentChannel(Messages.GUI_LOGFILE_LOGSETTINGS_PARENT_CHANNEL_0, String.class, ""); 111 112 /**Default value for column.*/ 113 private Object m_defaultValue; 114 115 /**Header Message key.*/ 116 private String m_headerMessage; 117 118 /**Type of column property.*/ 119 private Class<?> m_type; 120 121 /** 122 * constructor. 123 * 124 * @param headerMessage key 125 * @param type to property 126 * @param defaultValue of column 127 */ 128 TableColumn(String headerMessage, Class<?> type, Object defaultValue) { 129 130 m_headerMessage = headerMessage; 131 m_type = type; 132 m_defaultValue = defaultValue; 133 } 134 135 /** 136 * Returns list of all properties with non-empty header.<p> 137 * 138 * @return list of properties 139 */ 140 static List<TableColumn> withHeader() { 141 142 List<TableColumn> props = new ArrayList<TableColumn>(); 143 144 for (TableColumn prop : TableColumn.values()) { 145 if (prop.m_headerMessage != null) { 146 props.add(prop); 147 } 148 } 149 return props; 150 } 151 152 /** 153 * Returns the default value of property.<p> 154 * 155 * @return object 156 */ 157 Object getDefaultValue() { 158 159 return m_defaultValue; 160 } 161 162 /** 163 * Returns localized header.<p> 164 * 165 * @return string for header 166 */ 167 String getLocalizedMessage() { 168 169 if (m_headerMessage == null) { 170 return ""; 171 } 172 return CmsVaadinUtils.getMessageText(m_headerMessage); 173 } 174 175 /** 176 * Returns tye of value for given property.<p> 177 * 178 * @return type 179 */ 180 Class<?> getType() { 181 182 return m_type; 183 } 184 185 } 186 187 /** 188 * Enumeration of Logger Level and corresponging icon paths.<p> 189 */ 190 private enum LoggerLevel { 191 192 /**Debug level.*/ 193 Debug(Level.DEBUG, OpenCmsTheme.TABLE_COLUMN_BOX_RED, null), 194 195 /**Error level. */ 196 Error(Level.ERROR, OpenCmsTheme.TABLE_COLUMN_BOX_CYAN, "Default"), 197 198 /**Fatal level.*/ 199 Fatal(Level.FATAL, OpenCmsTheme.TABLE_COLUMN_BOX_BLUE_LIGHT, null), 200 201 /**Info level. */ 202 Info(Level.INFO, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE_DARK, null), 203 204 /**Off level. */ 205 Off(Level.OFF, OpenCmsTheme.TABLE_COLUMN_BOX_GRAY, null), 206 207 /**Warning level. */ 208 Warn(Level.WARN, OpenCmsTheme.TABLE_COLUMN_BOX_ORANGE, null); 209 210 /**Caption for logger level.*/ 211 private String m_caption; 212 213 /**CSS class.*/ 214 private String m_css; 215 216 /**Corresponging log4j Level.*/ 217 private Level m_level; 218 219 /** 220 * constructor.<p> 221 * 222 * @param level of logger 223 * @param css class 224 * @param caption for the level 225 */ 226 private LoggerLevel(Level level, String css, String caption) { 227 228 m_css = css; 229 m_level = level; 230 m_caption = caption; 231 } 232 233 /** 234 * Returns LoggerLevel object from given logger.<p> 235 * 236 * @param logger to fing enumeration object for 237 * @return LoggerLevel 238 */ 239 protected static LoggerLevel fromLogger(Logger logger) { 240 241 for (LoggerLevel item : LoggerLevel.values()) { 242 if (item.getLevel().equals(logger.getLevel())) { 243 return item; 244 } 245 } 246 return null; 247 } 248 249 /** 250 * Returns path to icon.<p> 251 * 252 * @return path to icon 253 */ 254 protected String getCssClass() { 255 256 return m_css; 257 } 258 259 /** 260 * Returns level. <p> 261 * @return log4j Level 262 */ 263 protected Level getLevel() { 264 265 return m_level; 266 } 267 268 /** 269 * Returns the string representation for level.<p> 270 * 271 * @return string 272 */ 273 protected String getLevelString() { 274 275 if (m_caption == null) { 276 String out = m_level.toString(); 277 return out.substring(0, 1).toUpperCase() + out.substring(1).toLowerCase(); 278 } 279 return m_caption; 280 } 281 282 /** 283 * Returns an extenden string representation with log level name added in case of having caption set.<p> 284 * 285 * @return string 286 */ 287 protected String getLevelStringComplet() { 288 289 if (m_caption == null) { 290 return getLevelString(); 291 } 292 String level = m_level.toString(); 293 level = level.substring(0, 1).toUpperCase() + level.substring(1).toLowerCase(); 294 return m_caption + " (" + level + ")"; 295 } 296 297 } 298 299 /** The prefix of opencms classes. */ 300 private static final String OPENCMS_CLASS_PREFIX = "org.opencms"; 301 302 /**vaadin serial id.*/ 303 private static final long serialVersionUID = 5467369614234190999L; 304 305 /**Container holding table data. */ 306 private IndexedContainer m_container; 307 308 /**Context menu. */ 309 private CmsContextMenu m_menu; 310 311 /** 312 * constructor.<p> 313 */ 314 protected CmsLogChannelTable() { 315 316 m_container = new IndexedContainer(); 317 318 setContainerDataSource(m_container); 319 320 for (TableColumn prop : TableColumn.values()) { 321 m_container.addContainerProperty(prop, prop.getType(), prop.getDefaultValue()); 322 setColumnHeader(prop, prop.getLocalizedMessage()); 323 } 324 325 setVisibleColumns(TableColumn.Level, TableColumn.Channel, TableColumn.ParentChannel, TableColumn.File); 326 327 setItemIconPropertyId(TableColumn.Icon); 328 setColumnWidth(null, 40); 329 setRowHeaderMode(RowHeaderMode.ICON_ONLY); 330 331 setColumnWidth(TableColumn.Level, 80); 332 333 setSelectable(true); 334 setMultiSelect(true); 335 m_menu = new CmsContextMenu(); 336 m_menu.setAsTableContextMenu(this); 337 addItemClickListener(new ItemClickListener() { 338 339 private static final long serialVersionUID = 1L; 340 341 public void itemClick(ItemClickEvent event) { 342 343 onItemClick(event, event.getItemId(), event.getPropertyId()); 344 } 345 }); 346 setCellStyleGenerator(new CellStyleGenerator() { 347 348 private static final long serialVersionUID = 1L; 349 350 public String getStyle(Table source, Object itemId, Object propertyId) { 351 352 if (TableColumn.Channel.equals(propertyId)) { 353 return " " + OpenCmsTheme.HOVER_COLUMN; 354 } 355 356 if (TableColumn.Level.equals(propertyId)) { 357 return ((LoggerLevel)source.getItem(itemId).getItemProperty(propertyId).getValue()).getCssClass(); 358 } 359 360 return null; 361 } 362 }); 363 364 addGeneratedColumn(TableColumn.Level, new LevelIcon()); 365 366 fillTable(); 367 } 368 369 /** 370 * Adds a container item for the given logger.<p> 371 * 372 * @param logger the logger for which to generate a container item 373 */ 374 public void addItemForLogger(Logger logger) { 375 376 Item item = m_container.addItem(logger); 377 if (item != null) { 378 item.getItemProperty(TableColumn.Channel).setValue(logger.getName()); 379 Logger parent = logger.getParent(); 380 item.getItemProperty(TableColumn.ParentChannel).setValue(parent != null ? parent.getName() : "none"); 381 item.getItemProperty(TableColumn.File).setValue(getLogFiles(logger)); 382 item.getItemProperty(TableColumn.Level).setValue(LoggerLevel.fromLogger(logger)); 383 } 384 } 385 386 /** 387 * Filters the table according to given search string.<p> 388 * 389 * @param search string to be looked for. 390 */ 391 @SuppressWarnings("unchecked") 392 public void filterTable(String search) { 393 394 m_container.removeAllContainerFilters(); 395 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) { 396 m_container.addContainerFilter( 397 new Or( 398 new SimpleStringFilter(TableColumn.Channel, search, true, false), 399 new SimpleStringFilter(TableColumn.ParentChannel, search, true, false), 400 new SimpleStringFilter(TableColumn.File, search, true, false))); 401 } 402 if ((getValue() != null) & !((Set<Logger>)getValue()).isEmpty()) { 403 setCurrentPageFirstItemId(((Set<Logger>)getValue()).iterator().next()); 404 } 405 } 406 407 /** 408 * Simple check if the logger has the global log file <p> or a single one. 409 * 410 * @param logchannel the channel that has do be checked 411 * @return true if the the log channel has a single log file 412 * */ 413 protected boolean isloggingactivated(Logger logchannel) { 414 415 boolean check = false; 416 for (Appender appender : logchannel.getAppenders().values()) { 417 check = appender.getName().equals(logchannel.getName()); 418 } 419 return check; 420 } 421 422 /** 423 * Toggles the log file of a given log channel.<p> 424 * 425 * @param logchannel to set or remove log file to 426 */ 427 @SuppressWarnings({"rawtypes", "unchecked"}) 428 protected void toggleOwnFile(Logger logchannel) { 429 430 String filepath = ""; 431 432 Layout layout = null; 433 // if the button is activated check the value of the button 434 // the button was active 435 if (isloggingactivated(logchannel)) { 436 // remove the private Appender from logger 437 for (Appender appender : logchannel.getAppenders().values()) { 438 logchannel.removeAppender(appender); 439 } 440 // activate the heredity so the logger get the appender from parent logger 441 logchannel.setAdditive(true); 442 443 } 444 // the button was inactive 445 else { 446 // get the layout and file path from root logger 447 for (Appender appender : ((Logger)LogManager.getRootLogger()).getAppenders().values()) { 448 if (CmsLogFileApp.isFileAppender(appender)) { 449 String fileName = CmsLogFileApp.getFileName(appender); 450 filepath = fileName.substring(0, fileName.lastIndexOf(File.separatorChar)); 451 layout = appender.getLayout(); 452 break; 453 } 454 } 455 456 // check if the logger has an Appender get his layout 457 for (Appender appender : logchannel.getAppenders().values()) { 458 if (CmsLogFileApp.isFileAppender(appender)) { 459 layout = appender.getLayout(); 460 break; 461 } 462 } 463 String logfilename = ""; 464 String temp = logchannel.getName(); 465 // check if the logger name begins with "org.opencms" 466 if (logchannel.getName().contains(OPENCMS_CLASS_PREFIX)) { 467 // remove the prefix "org.opencms" from logger name to generate the file name 468 temp = temp.replace(OPENCMS_CLASS_PREFIX, ""); 469 // if the name has suffix 470 if (temp.length() >= 1) { 471 logfilename = filepath + File.separator + "opencms-" + temp.substring(1).replace(".", "-") + ".log"; 472 } 473 // if the name has no suffix 474 else { 475 logfilename = filepath + File.separator + "opencms" + temp.replace(".", "-") + ".log"; 476 } 477 } 478 // if the logger name not begins with "org.opencms" 479 else { 480 logfilename = filepath + File.separator + "opencms-" + temp.replace(".", "-") + ".log"; 481 } 482 483 FileAppender fapp = ((Builder)FileAppender.<FileAppender.Builder> newBuilder().withFileName( 484 logfilename).withLayout(layout).withName(logchannel.getName())).build(); 485 486 // deactivate the heredity so the logger get no longer the appender from parent logger 487 logchannel.setAdditive(false); 488 // remove all active Appenders from logger 489 for (Appender appender : logchannel.getAppenders().values()) { 490 logchannel.removeAppender(appender); 491 } 492 // add the new created Appender to the logger 493 logchannel.addAppender(fapp); 494 } 495 updateFile(); 496 } 497 498 /** 499 * Sets a given Level to a Set of Loggers.<p> 500 * 501 * @param clickedLevel to be set 502 * @param clickedLogger to get level changed 503 */ 504 void changeLoggerLevel(LoggerLevel clickedLevel, Set<Logger> clickedLogger) { 505 506 for (Logger logger : clickedLogger) { 507 @SuppressWarnings("resource") 508 LoggerContext context = logger.getContext(); 509 Configuration config = context.getConfiguration(); 510 LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName()); 511 LoggerConfig specificConfig = loggerConfig; 512 if (!loggerConfig.getName().equals(logger.getName())) { 513 specificConfig = new LoggerConfig(logger.getName(), clickedLevel.getLevel(), true); 514 specificConfig.setParent(loggerConfig); 515 config.addLogger(logger.getName(), specificConfig); 516 } 517 specificConfig.setLevel(clickedLevel.getLevel()); 518 context.updateLoggers(); 519 } 520 updateLevel(); 521 } 522 523 /** 524 * Handles the table item clicks, including clicks on images inside of a table item.<p> 525 * 526 * @param event the click event 527 * @param itemId of the clicked row 528 * @param propertyId column id 529 */ 530 @SuppressWarnings("unchecked") 531 void onItemClick(MouseEvents.ClickEvent event, Object itemId, Object propertyId) { 532 533 if (!event.isCtrlKey() && !event.isShiftKey()) { 534 535 if (event.getButton().equals(MouseButton.LEFT)) { 536 setValue(null); 537 } 538 changeValueIfNotMultiSelect(itemId); 539 // don't interfere with multi-selection using control key 540 if (event.getButton().equals(MouseButton.RIGHT) || (propertyId == null)) { 541 m_menu.removeAllItems(); 542 fillContextMenu((Set<Logger>)getValue()); 543 m_menu.openForTable(event, itemId, propertyId, this); 544 } 545 546 } 547 } 548 549 /** 550 * Checks value of table and sets it new if needed:<p> 551 * if multiselect: new itemId is in current Value? -> no change of value<p> 552 * no multiselect and multiselect, but new item not selected before: set value to new item<p> 553 * 554 * @param itemId if of clicked item 555 */ 556 private void changeValueIfNotMultiSelect(Object itemId) { 557 558 @SuppressWarnings("unchecked") 559 Set<String> value = (Set<String>)getValue(); 560 if (value == null) { 561 select(itemId); 562 } else if (!value.contains(itemId)) { 563 setValue(null); 564 select(itemId); 565 } 566 } 567 568 /** 569 * Fills the context menu.<p> 570 * 571 * @param loggerSet Set of logger to open context menu for 572 */ 573 private void fillContextMenu(final Set<Logger> loggerSet) { 574 575 for (LoggerLevel level : LoggerLevel.values()) { 576 final LoggerLevel currentLevel = level; 577 ContextMenuItem item = m_menu.addItem(level.getLevelStringComplet()); 578 item.setData(loggerSet); 579 item.addItemClickListener(new ContextMenuItemClickListener() { 580 581 public void contextMenuItemClicked(ContextMenuItemClickEvent event) { 582 583 changeLoggerLevel(currentLevel, loggerSet); 584 585 } 586 }); 587 if (loggerSet.size() == 1) { 588 if (level.getLevel().equals(loggerSet.iterator().next().getLevel())) { 589 item.setIcon(FontOpenCms.CHECK_SMALL); 590 } 591 } 592 } 593 if (loggerSet.size() == 1) { 594 String message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_NEWFILE_0); 595 if (isloggingactivated(loggerSet.iterator().next())) { 596 message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGFILE_LOGSETTINGS_REMOVEFILE_0); 597 } 598 ContextMenuItem item = m_menu.addItem(message); 599 item.setData(loggerSet); 600 item.addItemClickListener(new ContextMenuItemClickListener() { 601 602 public void contextMenuItemClicked(ContextMenuItemClickEvent event) { 603 604 toggleOwnFile(loggerSet.iterator().next()); 605 606 } 607 }); 608 } 609 } 610 611 /** 612 * Populate table.<p> 613 */ 614 @SuppressWarnings("deprecation") 615 private void fillTable() { 616 617 removeAllItems(); 618 List<Logger> loggerList = CmsLog4jUtil.getAllLoggers(); 619 for (Logger logger : loggerList) { 620 addItemForLogger(logger); 621 } 622 } 623 624 /** 625 * Returns log files to given Logger.<p> 626 * 627 * @param logger to read files for 628 * @return path of file 629 */ 630 private String getLogFiles(Logger logger) { 631 632 String test = ""; 633 int count = 0; 634 // select the Appender from logger 635 for (Appender appender : logger.getAppenders().values()) { 636 // only use file appenders 637 if (CmsLogFileApp.isFileAppender(appender)) { 638 String fileName = CmsLogFileApp.getFileName(appender); 639 String temp = ""; 640 temp = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1); 641 test = test + temp; 642 count++; 643 break; 644 } 645 } 646 647 //iterate all parent loggers until a logger with appender was found 648 while (!logger.equals(LogManager.getRootLogger())) { 649 650 logger = logger.getParent(); 651 // if no Appender found from logger, select the Appender from parent logger 652 if (count == 0) { 653 for (Appender appender : logger.getAppenders().values()) { 654 // only use file appenders 655 if (CmsLogFileApp.isFileAppender(appender)) { 656 String fileName = CmsLogFileApp.getFileName(appender); 657 String temp = ""; 658 temp = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1); 659 test = test + temp; 660 count++; 661 break; 662 } 663 } 664 } 665 } 666 667 return test; 668 } 669 670 /** 671 * Updates table after change of file.<p> 672 */ 673 private void updateFile() { 674 675 @SuppressWarnings("unchecked") 676 List<Logger> logger = (List<Logger>)m_container.getItemIds(); 677 678 for (Logger item : logger) { 679 m_container.getItem(item).getItemProperty(TableColumn.File).setValue(getLogFiles(item)); 680 } 681 resetPageBuffer(); 682 refreshRenderedCells(); 683 refreshRowCache(); 684 } 685 686 /** 687 * Updates table after change of Level.<p> 688 */ 689 private void updateLevel() { 690 691 @SuppressWarnings("unchecked") 692 List<Logger> logger = (List<Logger>)m_container.getItemIds(); 693 694 for (Logger item : logger) { 695 m_container.getItem(item).getItemProperty(TableColumn.Level).setValue(LoggerLevel.fromLogger(item)); 696 } 697 resetPageBuffer(); 698 refreshRenderedCells(); 699 refreshRowCache(); 700 } 701}