/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.gui.log.internal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.lang3.text.WordUtils; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.management.WorkflowHostSetListener; import de.rcenvironment.core.gui.resources.api.ColorManager; import de.rcenvironment.core.gui.resources.api.ImageManager; import de.rcenvironment.core.gui.resources.api.StandardColors; import de.rcenvironment.core.gui.resources.api.StandardImages; import de.rcenvironment.core.gui.utils.common.ClipboardHelper; import de.rcenvironment.core.log.SerializableLogEntry; import de.rcenvironment.core.utils.incubator.ServiceRegistry; import de.rcenvironment.core.utils.incubator.ServiceRegistryPublisherAccess; /** * The whole log view. * * @author Enrico Tappert * @author Doreen Seider * @author Robert Mischke * @author Oliver Seebach */ public class LogView extends ViewPart { /** Identifier used to find the this view within the workbench. */ public static final String ID = "de.rcenvironment.rce.gui.log.view"; //$NON-NLS-1$ private static LogView myInstance; private static final int TEXT_AREA_WORD_WRAP_WIDTH = 180; private static final boolean ERROR_PRESELECTED = true; private static final boolean INFO_PRESELECTED = true; private static final boolean WARN_PRESELECTED = true; private static final int COLUMN_WIDTH_BUNDLE = 250; private static final int COLUMN_WIDTH_PLATFORM = 250; private static final int COLUMN_WIDTH_LEVEL = 70; private static final int COLUMN_WIDTH_MESSAGE = 250; private static final int COLUMN_WIDTH_TIME = 140; private static final int NO_SPACE = 0; private static final int PLATFORM_WIDTH = 250; private static final int TEXT_WIDTH = PLATFORM_WIDTH; private Button myCheckboxError; private Button myCheckboxInfo; private Button myCheckboxWarn; private Combo myPlatformCombo; private LogTableFilter myListenerAndFilter; private LogTableColumnSorter myTableColumnSorter; private TableViewer myLogEntryTableViewer; private Text myMessageTextArea; private Text mySearchTextField; private Label searchWarning; private SerializableLogEntry displayedLogEntry; private boolean scrollLocked = false; private boolean adjustedSearchMessage = false; private Action clearAction = null; private Action copyAction = null; private Action scrollLockAction = null; private final LogModel.Listener listener = new LogModel.Listener() { @Override public void handleLogEntryAdded(final SerializableLogEntry logEntry) { if (!myLogEntryTableViewer.getTable().isDisposed() && !myLogEntryTableViewer.getTable().getDisplay().isDisposed()) { myLogEntryTableViewer.getTable().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!myLogEntryTableViewer.getTable().isDisposed()) { if (logEntry.getPlatformIdentifer().equals(getPlatform())) { myLogEntryTableViewer.add(logEntry); if (!LogView.this.isScrollLocked()) { myLogEntryTableViewer.reveal(logEntry); } } } } }); } } @Override public void handleLogEntryRemoved(final SerializableLogEntry logEntry) { /* * Log entries are also removed, because otherwise upon a resort the log entries would just vanish ... removing them thus * implements the principle of least astonishment. */ myLogEntryTableViewer.getTable().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!myLogEntryTableViewer.getTable().isDisposed()) { if (logEntry.getPlatformIdentifer().equals(getPlatform())) { myLogEntryTableViewer.remove(logEntry); } } } }); /** * If (the message of) the deleted log entry is displayed, clear the displayed message. */ if (logEntry.equals(getDisplayedLogEntry())) { myLogEntryTableViewer.getTable().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!myMessageTextArea.isDisposed()) { displayLogEntry(null); } } }); } } }; private ServiceRegistryPublisherAccess serviceRegistryAccess; private Display display; public LogView() { myInstance = this; } public static LogView getInstance() { return myInstance; } @Override public void createPartControl(final Composite parent) { parent.setLayout(new GridLayout(1, false)); // filter = level selection, platform selection, and search text field Composite filterComposite = new Composite(parent, SWT.NONE); filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); filterComposite.setLayout(new RowLayout()); createLevelArrangement(filterComposite); createPlatformListingArrangement(filterComposite); createSearchArrangement(filterComposite); // sash = table and text display Composite sashComposite = new Composite(parent, SWT.NONE); sashComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); sashComposite.setLayout(new GridLayout(1, false)); SashForm sashForm = new SashForm(sashComposite, SWT.VERTICAL | SWT.SMOOTH); sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); createTableArrangement(sashForm); createTableDynamics(); createTextAreaArrangement(sashForm); sashForm.setWeights(new int[] { 3, 1 }); sashForm.setSashWidth(7); // add sorting functionality myTableColumnSorter = new LogTableColumnSorter(); myLogEntryTableViewer.setSorter(myTableColumnSorter); // add change listeners and filter mechanism myListenerAndFilter = new LogTableFilter(this, myLogEntryTableViewer); mySearchTextField.addKeyListener(myListenerAndFilter); myCheckboxError.addSelectionListener(myListenerAndFilter); myCheckboxInfo.addSelectionListener(myListenerAndFilter); myCheckboxWarn.addSelectionListener(myListenerAndFilter); myPlatformCombo.addSelectionListener(myListenerAndFilter); myLogEntryTableViewer.addFilter(myListenerAndFilter); initActions(); // add toolbar actions (right top of view) for (Action action : createToolbarActions()) { getViewSite().getActionBars().getToolBarManager().add(action); } getSite().setSelectionProvider(myLogEntryTableViewer); hookContextMenu(); // disable copy action when table or selection is empty myLogEntryTableViewer.getTable().addMenuDetectListener(new MenuDetectListener() { @Override public void menuDetected(MenuDetectEvent arg0) { if (myLogEntryTableViewer.getSelection().isEmpty() || myLogEntryTableViewer.getTable().getItemCount() == 0) { copyAction.setEnabled(false); } else { copyAction.setEnabled(true); } } }); // store display reference for access by topology listener display = parent.getShell().getDisplay(); registerWorkflowHostSetListener(); } /** * Registers an event listener for network changes as an OSGi service (whiteboard pattern). * * @param display */ private void registerWorkflowHostSetListener() { serviceRegistryAccess = ServiceRegistry.createPublisherAccessFor(this); serviceRegistryAccess.registerService(WorkflowHostSetListener.class, new WorkflowHostSetListener() { @Override public void onReachableWorkflowHostsChanged(Set<InstanceNodeSessionId> reachableWfHosts, Set<InstanceNodeSessionId> addedWfHosts, Set<InstanceNodeSessionId> removedWfHosts) { final List<InstanceNodeSessionId> nodeIds = LogModel.getInstance().updateListOfLogSources(); display.asyncExec(new Runnable() { @Override public void run() { refreshPlatformCombo(nodeIds); } }); } }); } /** * Triggers asynchronous refresh (worker thread calls UI thread) of the view only for the given platform. * * @param platform Current selected platform. */ private void asyncRefresh(final InstanceNodeSessionId platform) { myLogEntryTableViewer.getTable().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!myLogEntryTableViewer.getTable().isDisposed()) { try { if (platform.equals(getPlatform())) { displayLogEntry(null); myLogEntryTableViewer.getTable().clearAll(); myLogEntryTableViewer.refresh(); } } catch (SWTException e) { // re-throw the exception if the disposal state is NOT the error if (!myLogEntryTableViewer.getTable().isDisposed()) { throw e; } } } } }); } public boolean getErrorSelection() { return myCheckboxError.getSelection(); } public boolean getInfoSelection() { return myCheckboxInfo.getSelection(); } public boolean getWarnSelection() { return myCheckboxWarn.getSelection(); } public InstanceNodeSessionId getPlatform() { return (InstanceNodeSessionId) myPlatformCombo.getData(myPlatformCombo.getItem(myPlatformCombo.getSelectionIndex())); } /** * Method is deleting not allowed searchMessages. * * @return Returns the searchMessage * @author Mark Geiger */ public String getSearchText() { boolean setVisible = false; boolean containsRow = false; String ret = ""; if (adjustedSearchMessage) { adjustedSearchMessage = false; setVisible = true; } do { containsRow = false; int inRow = 0; int rowStartIdx = 0; ret = mySearchTextField.getText().toString(); for (int i = 0; i < ret.length(); i++) { if (ret.charAt(i) == '*') { if (inRow == 0) { rowStartIdx = i; } inRow++; } else { inRow = 0; } if (inRow > 1) { setVisible = true; containsRow = true; // cut of '*******' mySearchTextField.setText( mySearchTextField.getText().toString().substring(0, rowStartIdx) + mySearchTextField.getText().toString().substring(i, ret.length())); mySearchTextField.setSelection(rowStartIdx + 1); adjustedSearchMessage = true; break; } } } while (containsRow); searchWarning.setVisible(setVisible); return ret; } @Override public void setFocus() { myLogEntryTableViewer.getControl().setFocus(); } /** * * Create the composite structure with level selection options. * * @param filterComposite Parent composite for the level selection options. */ private void createLevelArrangement(Composite filterComposite) { RowLayout rowLayout = new RowLayout(); rowLayout.spacing = NO_SPACE; filterComposite.setLayout(rowLayout); // ERROR checkbox myCheckboxError = new Button(filterComposite, SWT.CHECK); myCheckboxError.setText(Messages.error); myCheckboxError.setSelection(ERROR_PRESELECTED); // WARN checkbox myCheckboxWarn = new Button(filterComposite, SWT.CHECK); myCheckboxWarn.setText(Messages.warn); myCheckboxWarn.setSelection(WARN_PRESELECTED); // INFO checkbox myCheckboxInfo = new Button(filterComposite, SWT.CHECK); myCheckboxInfo.setText(Messages.info); myCheckboxInfo.setSelection(INFO_PRESELECTED); } /** * * Create the composite structure with platform selection options. * * @param platformComposite Parent composite for the platform selection options. */ private void createPlatformListingArrangement(Composite platformComposite) { RowLayout rowLayout = new RowLayout(); rowLayout.spacing = NO_SPACE; platformComposite.setLayout(rowLayout); // platform listing combo box myPlatformCombo = new Combo(platformComposite, SWT.DROP_DOWN | SWT.READ_ONLY); for (InstanceNodeSessionId nodeId : LogModel.getInstance().updateListOfLogSources()) { myPlatformCombo.add(nodeId.getAssociatedDisplayName()); myPlatformCombo.setData(nodeId.getAssociatedDisplayName(), nodeId); } myPlatformCombo.select(0); myPlatformCombo.setLayoutData(new RowData(PLATFORM_WIDTH, SWT.DEFAULT)); LogModel.getInstance().setSelectedLogSource((InstanceNodeSessionId) myPlatformCombo.getData( myPlatformCombo.getItem(myPlatformCombo.getSelectionIndex()))); } /** * * Create the composite structure with search options. * * @param searchComposite Parent composite for the search options. */ private void createSearchArrangement(Composite searchComposite) { RowLayout rowLayout = new RowLayout(); rowLayout.spacing = 7; rowLayout.center = true; rowLayout.fill = true; searchComposite.setLayout(rowLayout); // search text field mySearchTextField = new Text(searchComposite, SWT.SEARCH); mySearchTextField.setMessage(Messages.search); mySearchTextField.setSize(TEXT_WIDTH, SWT.DEFAULT); mySearchTextField.setLayoutData(new RowData(TEXT_WIDTH, SWT.DEFAULT)); Composite warningComp = new Composite(searchComposite, 0); warningComp.setLayout(rowLayout); searchWarning = new Label(warningComp, SWT.CENTER | SWT.BORDER); searchWarning.setVisible(false); searchWarning.setBackground(ColorManager.getInstance().getSharedColor(StandardColors.RCE_GERALDINE)); searchWarning.setFont(new Font(null, new FontData("TimesNewRoman", 8, 1))); searchWarning.setText(" Only one ' * ' in a row allowed "); } /** * * Create the composite structure with log display and selection options. * * @param platformComposite Parent composite for log display and selection options. */ private void createTableArrangement(Composite tableComposite) { tableComposite.setLayout(new GridLayout()); // create table viewer myLogEntryTableViewer = new TableViewer(tableComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI); myLogEntryTableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // create table header and column styles String[] columnTitles = new String[] { Messages.level, Messages.message, Messages.bundle, Messages.platform, Messages.timestamp }; int[] bounds = { COLUMN_WIDTH_LEVEL, COLUMN_WIDTH_MESSAGE, COLUMN_WIDTH_BUNDLE, COLUMN_WIDTH_PLATFORM, COLUMN_WIDTH_TIME }; for (int i = 0; i < columnTitles.length; i++) { // for all columns final int index = i; final TableViewerColumn viewerColumn = new TableViewerColumn(myLogEntryTableViewer, SWT.NONE); final TableColumn column = viewerColumn.getColumn(); // set column properties column.setText(columnTitles[i]); column.setWidth(bounds[i]); column.setResizable(true); column.setMoveable(true); column.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { myTableColumnSorter.setColumn(index); int direction = myLogEntryTableViewer.getTable().getSortDirection(); if (myLogEntryTableViewer.getTable().getSortColumn() == column) { if (direction == SWT.UP) { direction = SWT.DOWN; } else { direction = SWT.UP; } } else { direction = SWT.UP; } myLogEntryTableViewer.getTable().setSortDirection(direction); myLogEntryTableViewer.getTable().setSortColumn(column); myLogEntryTableViewer.getTable().clearAll(); myLogEntryTableViewer.refresh(); } }); } // set table content myLogEntryTableViewer.setContentProvider(new LogContentProvider()); myLogEntryTableViewer.setLabelProvider(new LogLabelProvider()); myLogEntryTableViewer.setInput(LogModel.getInstance()); // set table layout data final Table table = myLogEntryTableViewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // for the selection of a table item table.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event e) { TableItem[] selection = table.getSelection(); if (selection.length > 0) { if (selection[0].getData() instanceof SerializableLogEntry) { SerializableLogEntry log = (SerializableLogEntry) selection[0].getData(); displayLogEntry(log); } } } }); myLogEntryTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { if (event.getSelection().isEmpty()) { displayLogEntry(null); } } }); } private void hookContextMenu() { MenuManager menuManager = new MenuManager(); menuManager.add(clearAction); menuManager.add(copyAction); Menu menu = menuManager.createContextMenu(myLogEntryTableViewer.getTable()); myLogEntryTableViewer.getTable().setMenu(menu); getSite().registerContextMenu(menuManager, myLogEntryTableViewer); getSite().setSelectionProvider(myLogEntryTableViewer); } private void createTableDynamics() { LogModel.getInstance().addListener(listener); } private SerializableLogEntry getDisplayedLogEntry() { return displayedLogEntry; } protected void displayLogEntry(final SerializableLogEntry logEntry) { StringBuffer buffer = new StringBuffer(); if (logEntry != null) { buffer.append(logEntry.getMessage().replaceAll(SerializableLogEntry.RCE_SEPARATOR, "\n")); if (logEntry.getException() != null) { if (buffer.length() == 0) { buffer.append("(no message)"); } if (logEntry.getException() != null && !logEntry.getException().isEmpty()) { // note: there were line breaks added here before, but that doesn't make sense with compacted exceptions anymore if (buffer.charAt(buffer.length() - 1) == ':') { // avoid double colons buffer.append(" "); } else { buffer.append(": "); } buffer.append(logEntry.getException()); } } } String text = buffer.toString(); text = WordUtils.wrap(text, TEXT_AREA_WORD_WRAP_WIDTH, "\n", false); myMessageTextArea.setText(text); displayedLogEntry = logEntry; } @Override public void dispose() { LogModel.getInstance().removeListener(listener); super.dispose(); if (serviceRegistryAccess != null) { serviceRegistryAccess.dispose(); } } /** * * Create the composite structure for expansive text displaying. * * @param textAreaComposite Parent composite for expansive text displaying. */ private void createTextAreaArrangement(Composite textAreaComposite) { textAreaComposite.setLayout(new GridLayout(1, false)); myMessageTextArea = new Text(textAreaComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); myMessageTextArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); myMessageTextArea.setEditable(false); } private Action[] createToolbarActions() { return new Action[] { scrollLockAction, clearAction }; } protected void clear() { LogModel.getInstance().clear(); asyncRefresh(getPlatform()); } protected void setScrollLocked(final boolean scrollLocked) { this.scrollLocked = scrollLocked; } public boolean isScrollLocked() { return scrollLocked; } protected ImageDescriptor getScrollLockImageDescriptor() { final ImageDescriptor result; if (isScrollLocked()) { result = ImageManager.getInstance().getImageDescriptor(StandardImages.SCROLLOCK_ENABLED); } else { result = ImageManager.getInstance().getImageDescriptor(StandardImages.SCROLLOCK_DISABLED); } return result; } private void refreshPlatformCombo(List<InstanceNodeSessionId> nodeIds) { myPlatformCombo.removeAll(); for (InstanceNodeSessionId nodeId : nodeIds) { myPlatformCombo.add(nodeId.getAssociatedDisplayName()); myPlatformCombo.setData(nodeId.getAssociatedDisplayName(), nodeId); } LogModel logModel = LogModel.getInstance(); logModel.updateListOfLogSources(); // select platform (previously selected) InstanceNodeSessionId currentPlatform = logModel.getCurrentLogSource(); if (currentPlatform != null) { String[] items = myPlatformCombo.getItems(); for (int i = 0; i < items.length; i++) { if (myPlatformCombo.getData(items[i]).equals(currentPlatform)) { myPlatformCombo.select(i); return; } } } myPlatformCombo.select(0); logModel.setSelectedLogSource((InstanceNodeSessionId) myPlatformCombo.getData(myPlatformCombo.getItem(0))); asyncRefresh(logModel.getCurrentLogSource()); } private void initActions() { clearAction = new Action(Messages.clear, ImageDescriptor.createFromImage( PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE))) { @Override public void run() { clear(); } }; copyAction = new Action(Messages.copy, ImageDescriptor.createFromImage( PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY))) { @Override public void run() { ISelection selection = myLogEntryTableViewer.getSelection(); List<SerializableLogEntry> logEntries = new ArrayList<SerializableLogEntry>(); if (selection != null && selection instanceof IStructuredSelection) { IStructuredSelection sel = (IStructuredSelection) selection; for (@SuppressWarnings("unchecked") Iterator<SerializableLogEntry> iterator = sel.iterator(); iterator.hasNext();) { SerializableLogEntry logEntry = iterator.next(); logEntries.add(logEntry); } } StringBuilder sb = new StringBuilder(); for (SerializableLogEntry logEntry : logEntries) { sb.append(logEntry.toString() + System.getProperty("line.separator")); //$NON-NLS-1$ } if (sb.length() > 0) { ClipboardHelper.setContent(sb.toString()); } } }; scrollLockAction = new Action(Messages.scrollLock, SWT.TOGGLE) { { setImageDescriptor(getScrollLockImageDescriptor()); } @Override public void run() { setScrollLocked(!isScrollLocked()); setImageDescriptor(getScrollLockImageDescriptor()); } }; } }