com.twinsoft.convertigo.eclipse.views.loggers.EngineLogView.java Source code

Java tutorial

Introduction

Here is the source code for com.twinsoft.convertigo.eclipse.views.loggers.EngineLogView.java

Source

/*
 * Copyright (c) 2001-2011 Convertigo SA.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 * $URL: http://sourceus/svn/convertigo/CEMS_opensource/branches/6.2.x/Studio/src/com/twinsoft/convertigo/eclipse/views/projectexplorer/ClipboardManager2.java $
 * $Author: nicolasa $
 * $Revision: 31165 $
 * $Date: 2012-07-20 17:45:54 +0200 (ven., 20 juil. 2012) $
 */

package com.twinsoft.convertigo.eclipse.views.loggers;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
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.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.RetargetAction;
import org.eclipse.ui.part.ViewPart;

import com.twinsoft.convertigo.eclipse.ConvertigoPlugin;
import com.twinsoft.convertigo.eclipse.dialogs.EnginePreferenceDialog;
import com.twinsoft.convertigo.eclipse.dialogs.EventDetailsDialog;
import com.twinsoft.convertigo.eclipse.dialogs.EventDetailsDialogComposite;
import com.twinsoft.convertigo.eclipse.dialogs.LimitCharsLogsPreferenceDialog;
import com.twinsoft.convertigo.engine.Engine;
import com.twinsoft.convertigo.engine.admin.logmanager.LogManager;
import com.twinsoft.convertigo.engine.admin.services.ServiceException;
import com.twinsoft.convertigo.engine.util.GenericUtils;

public class EngineLogView extends ViewPart {
    private Thread logViewThread;
    private LogManager logManager;
    private long lastLogTime = -1;
    private Queue<LogLine> logLines = new ConcurrentLinkedQueue<LogLine>();
    private Appender appender;
    private int counter = 0;

    private long charMeter = 0;

    private boolean scrollLock = false;
    private boolean activateOnNewEvents = true;
    private ColumnInfo[] columnInfos = EngineLogView.clone(DEFAULT_COLUMN_INFOS);
    private int[] columnOrder = DEFAULT_COLUMN_ORDER.clone();
    private int limitLogChars = DEFAULT_MAX_LOG_CHARS;

    private static final int DEFAULT_MAX_LOG_CHARS = 100000;
    private static final int MAX_BUFFER_LINES = 50;

    private Action activateOnNewEventsAction, clearLogsAction, restoreDefaultsAction, selectColumnsAction,
            settingsEngine, limitLogCharsAction;
    private RetargetAction scrollLockAction, optionsAction, searchAction;

    private EngineLogViewLabelProvider labelProvider;

    private static final ColumnInfo[] DEFAULT_COLUMN_INFOS = { new ColumnInfo("Date", false, 80),
            new ColumnInfo("Time", true, 90), new ColumnInfo("DeltaTime", true, 60),
            new ColumnInfo("Message", true, 400), new ColumnInfo("Level", false, 50),
            new ColumnInfo("Category", true, 80), new ColumnInfo("Thread", true, 180),
            new ColumnInfo("Project", true, 70), new ColumnInfo("Connector", true, 70),
            new ColumnInfo("Transaction", true, 70), new ColumnInfo("Sequence", true, 70),
            new ColumnInfo("ContextID", true, 160), new ColumnInfo("UID", false, 50),
            new ColumnInfo("User", false, 50), new ColumnInfo("ClientIP", false, 50),
            new ColumnInfo("ClientHostName", false, 50), new ColumnInfo("UUID", false, 50) };

    private static final int[] DEFAULT_COLUMN_ORDER = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

    private static ColumnInfo[] clone(ColumnInfo[] array) {
        ColumnInfo[] clonedArray = array.clone();
        for (int i = 0; i < array.length; i++) {
            clonedArray[i] = (ColumnInfo) array[i].clone();
        }

        return clonedArray;
    }

    private Menu tableMenu;
    private MenuItem addVariableItem;

    private Text filterText, searchText;
    private Label infoSearch;
    private boolean searchCaseSensitive = false;

    private TableViewer tableViewer;
    private int selectedColumnIndex;

    public EngineLogView() {
        labelProvider = new EngineLogViewLabelProvider();
        appender = new AppenderSkeleton() {

            @Override
            protected void append(LoggingEvent arg0) {
                synchronized (this) {
                    this.notifyAll();
                }
            }

            public void close() {
            }

            public boolean requiresLayout() {
                return false;
            }
        };
    }

    @Override
    public void dispose() {
        Engine.logConvertigo.removeAppender(appender);

        logViewThread = null;

        // Notify the log viewer thread possibly waiting on the appender lock
        synchronized (appender) {
            appender.notifyAll();
        }

        super.dispose();
    }

    /*
     * View state persistence
     */

    private IMemento memento;

    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        this.memento = memento;
    }

    @Override
    public void saveState(IMemento memento) {
        // Scroll lock
        memento.putBoolean("scrollLock", scrollLock);

        // Limit log chars
        memento.putInteger("limitLogChars", limitLogChars);

        // Activate on new events
        memento.putBoolean("activateOnNewEvents", activateOnNewEvents);

        // Column order
        memento.putString("columnOrder", Arrays.toString(tableViewer.getTable().getColumnOrder()));

        // Column information
        for (ColumnInfo columnInfo : columnInfos) {
            IMemento columnInfoMemento = memento.createChild("columnInfo");
            columnInfoMemento.putString("name", columnInfo.getName());
            columnInfoMemento.putBoolean("visible", columnInfo.isVisible());
            columnInfoMemento.putInteger("size", columnInfo.getSize());
        }

        super.saveState(memento);
    }

    private void restoreState() {
        if (memento == null)
            return;

        // Scroll lock
        Boolean bScrollLock = memento.getBoolean("scrollLock");
        if (bScrollLock != null)
            scrollLock = bScrollLock.booleanValue();

        //Limit log chars
        Integer iLimitLogChars = memento.getInteger("limitLogChars");
        if (iLimitLogChars != null)
            limitLogChars = iLimitLogChars.intValue();

        // Activate on new events
        Boolean bActivateOnNewEvents = memento.getBoolean("activateOnNewEvents");
        if (bActivateOnNewEvents != null)
            activateOnNewEvents = bActivateOnNewEvents.booleanValue();

        // Column order
        String columnOrderAsString = memento.getString("columnOrder");
        if (columnOrderAsString != null) {
            columnOrderAsString = columnOrderAsString.substring(1, columnOrderAsString.length() - 1);
            String[] columnOrders = columnOrderAsString.split(",");
            for (int i = 0; i < columnOrders.length; i++) {
                String column = columnOrders[i].trim();
                try {
                    columnOrder[i] = Integer.parseInt(column);
                } catch (Exception e) {
                    // Silently ignore (use default column order)
                }
            }
        }

        // Column information
        IMemento[] mementoColumnInfos = memento.getChildren("columnInfo");
        int i = 0;
        for (IMemento mementoColumnInfo : mementoColumnInfos) {
            columnInfos[i] = new ColumnInfo(mementoColumnInfo.getString("name"),
                    mementoColumnInfo.getBoolean("visible"), mementoColumnInfo.getInteger("size"));
            i++;
        }
    }

    private Composite mainComposite;

    @Override
    public void createPartControl(Composite parent) {
        restoreState();

        mainComposite = parent;

        GridLayout layout = new GridLayout(1, false);
        parent.setLayout(layout);
        layout.marginWidth = 0;
        layout.horizontalSpacing = 0;
        layout.verticalSpacing = 0;

        // Options composite
        createOptions(parent);

        // Search composite
        createSearch(parent);

        // Table viewer composite
        createTableViewer(parent);

        createActions();
        createToolbar();
        createMenu();

        createLogViewThread();
    }

    /*
     * Options
     */

    private Composite compositeOptions;

    private void createOptions(Composite parent) {
        compositeOptions = new Composite(parent, SWT.NONE);
        compositeOptions.setVisible(false);

        GridData layoutData;

        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        layoutData.exclude = true;
        compositeOptions.setLayoutData(layoutData);

        GridLayout layout = new GridLayout(4, false);
        compositeOptions.setLayout(layout);
        layout.marginWidth = 3;

        Label label = new Label(compositeOptions, SWT.NONE);
        label.setText("Filter");

        filterText = new Text(compositeOptions, SWT.BORDER);
        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        filterText.setLayoutData(layoutData);
        filterText.addListener(SWT.DefaultSelection, new Listener() {
            public void handleEvent(Event e) {
                clearLogs();
                setLogFilter();
            }
        });

        Button applyButton = new Button(compositeOptions, SWT.NONE);
        applyButton.setText("Apply");
        applyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                clearLogs();
                setLogFilter();
            }
        });

        Button clearButton = new Button(compositeOptions, SWT.NONE);
        clearButton.setText("Clear");
        clearButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                filterText.setText("");
                clearLogs();
                setLogFilter();
            }
        });
    }

    private void setLogFilter() {
        try {
            logManager.setFilter(filterText.getText());
        } catch (ServiceException e) {
            ConvertigoPlugin.logException(e, "Unable to apply the filter", true);
        }
    }

    /*
     * Search
     */

    private Button previousSearch, nextSearch;
    private Composite compositeSearch;

    private void createSearch(Composite parent) {
        compositeSearch = new Composite(parent, SWT.NONE);
        compositeSearch.setVisible(false);

        GridData layoutData;

        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        layoutData.exclude = true;
        compositeSearch.setLayoutData(layoutData);

        GridLayout layout = new GridLayout(8, false);
        compositeSearch.setLayout(layout);
        layout.marginWidth = 3;

        Label label = new Label(compositeSearch, SWT.NONE);
        label.setText("Search");

        searchText = new Text(compositeSearch, SWT.BORDER);
        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        searchText.setLayoutData(layoutData);
        searchText.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                applySearch();
            }
        });

        infoSearch = new Label(compositeSearch, SWT.NONE);
        infoSearch.setVisible(false);

        previousSearch = new Button(compositeSearch, SWT.NONE);
        previousSearch.setText(" < ");
        previousSearch.setEnabled(false);
        previousSearch.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                searchInLogs(-1);
            }
        });

        nextSearch = new Button(compositeSearch, SWT.NONE);
        nextSearch.setText(" > ");
        nextSearch.setEnabled(false);
        nextSearch.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                searchInLogs(1);
            }
        });

        Button checkCase = new Button(compositeSearch, SWT.CHECK);
        checkCase.setText("Case sensitive");
        checkCase.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                searchCaseSensitive = ((Button) e.widget).getSelection();
                applySearch();
            }
        });

        Button applyButton = new Button(compositeSearch, SWT.NONE);
        applyButton.setText("Apply");
        applyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                applySearch();
            }
        });

        Button clearButton = new Button(compositeSearch, SWT.NONE);
        clearButton.setText("Clear");
        clearButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                searchText.setText("");
                applySearch();
            }
        });
    }

    private int currentFoundIndex = 0;
    private ArrayList<Integer> foundIndexes = new ArrayList<Integer>();

    private void applySearch() {
        currentFoundIndex = 0;
        nextSearch.setEnabled(false);
        previousSearch.setEnabled(false);

        String searchedText = searchText.getText();
        if ("".equals(searchedText)) {
            infoSearch.setVisible(false);
            GridData data = (GridData) infoSearch.getLayoutData();
            data.exclude = true;
            compositeSearch.layout();
            return;
        }

        foundIndexes.clear();
        Table table = tableViewer.getTable();
        int nLines = table.getItemCount();

        if (!searchCaseSensitive) {
            searchedText = searchedText.toLowerCase();
        }

        for (int i = 0; i < nLines; i++) {
            String cellText = table.getItem(i).getText(3);

            if (!searchCaseSensitive) {
                cellText = cellText.toLowerCase();
            }

            if (cellText.contains(searchedText))
                foundIndexes.add(i);
        }

        if (foundIndexes.isEmpty()) {
            infoSearch.setText("0 / 0");
        } else {
            int nFoundOccurrences = foundIndexes.size();
            infoSearch.setText((currentFoundIndex + 1) + "/" + nFoundOccurrences);
            tableViewer.getTable().setSelection(foundIndexes.get(currentFoundIndex));
            tableViewer.getTable().setFocus();
            nextSearch.setEnabled(nFoundOccurrences > 1);
        }

        infoSearch.setVisible(true);
        GridData data = (GridData) infoSearch.getLayoutData();
        data.exclude = false;
        compositeSearch.layout();
    }

    private void searchInLogs(int side) {
        int searchIndex = currentFoundIndex + side;
        if (searchIndex < 0 || searchIndex > foundIndexes.size())
            return;

        currentFoundIndex = searchIndex;
        Table table = tableViewer.getTable();

        table.setSelection(foundIndexes.get(currentFoundIndex));
        table.setFocus();

        // Disable "previous" if is the beginning
        if (foundIndexes.get(currentFoundIndex) == foundIndexes.get(0))
            previousSearch.setEnabled(false);
        else
            previousSearch.setEnabled(true);

        // Disable "next" if is the end
        if (foundIndexes.get(currentFoundIndex) == foundIndexes.get(foundIndexes.size() - 1))
            nextSearch.setEnabled(false);
        else
            nextSearch.setEnabled(true);

        infoSearch.setText((currentFoundIndex + 1) + "/" + foundIndexes.size());

        // To force components resizing if needed
        compositeSearch.layout();
    }

    /*
     * Table viewer
     */

    private Composite compositeTableViewer;

    private void createTableViewer(Composite parent) {
        compositeTableViewer = new Composite(parent, SWT.NONE);

        GridData layoutData;

        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.verticalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        layoutData.grabExcessVerticalSpace = true;
        compositeTableViewer.setLayoutData(layoutData);

        GridLayout layout = new GridLayout(1, false);
        compositeTableViewer.setLayout(layout);
        layout.marginWidth = 10;

        tableViewer = new TableViewer(compositeTableViewer, SWT.RESIZE | SWT.H_SCROLL | SWT.V_SCROLL
                | SWT.FULL_SELECTION | SWT.BORDER | SWT.VERTICAL | SWT.FILL);

        layoutData = new GridData();
        layoutData.horizontalAlignment = SWT.FILL;
        layoutData.verticalAlignment = SWT.FILL;
        layoutData.grabExcessHorizontalSpace = true;
        layoutData.grabExcessVerticalSpace = true;
        tableViewer.getTable().setLayoutData(layoutData);

        createColumns();
        createContextualTableViewerMenu();

        final Table table = tableViewer.getTable();
        table.setHeaderVisible(true);
        table.pack();
        tableViewer.setLabelProvider(labelProvider);
        tableViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                ISelection selection = event.getSelection();
                if (selection instanceof IStructuredSelection) {
                    Object selectedObject = ((IStructuredSelection) selection).getFirstElement();
                    if (selectedObject instanceof LogLine) {
                        LogLine logLine = (LogLine) selectedObject;
                        EventDetailsDialog dialog = new EventDetailsDialog(Display.getCurrent().getActiveShell(),
                                EventDetailsDialogComposite.class, "Event Details", logLine);
                        dialog.open();
                    }
                }
            }
        });

        table.addMenuDetectListener(new MenuDetectListener() {
            public void menuDetected(MenuDetectEvent event) {
                Point pt = Display.getCurrent().map(null, table, new Point(event.x, event.y));
                if (tableViewer.getCell(pt) != null) {
                    selectedColumnIndex = tableViewer.getCell(pt).getColumnIndex();
                    addVariableItem.setEnabled(selectedColumnIndex > 4 && selectedColumnIndex < 14 ? true : false);
                }
            }
        });

        /*
         * IMPORTANT: Dispose the menus (only the current menu, set with
         * setMenu(), will be automatically disposed)
         */
        table.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                tableMenu.dispose();
            }
        });

        // Make the selection available to other views
        getSite().setSelectionProvider(tableViewer);
    }

    private ColumnInfo getColumnInfo(String columnName) {
        // Searching for the default column info
        for (ColumnInfo columnInfo : columnInfos) {
            if (columnInfo.getName().equals(columnName)) {
                return columnInfo;
            }
        }

        throw new NoSuchElementException("Column info for '" + columnName + "' not found");
    }

    private void createActions() {
        optionsAction = new RetargetAction("Toggle", "Options", IAction.AS_CHECK_BOX) {
            public void runWithEvent(Event event) {
                GridData data = (GridData) compositeOptions.getLayoutData();
                data.exclude = compositeOptions.isVisible();
                compositeOptions.setVisible(!compositeOptions.isVisible());
                mainComposite.layout(true);
            }
        };
        optionsAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/options.png"))));
        optionsAction.setEnabled(true);
        optionsAction.setChecked(false);

        searchAction = new RetargetAction("Toggle", "Search", IAction.AS_CHECK_BOX) {
            public void runWithEvent(Event event) {
                GridData data = (GridData) compositeSearch.getLayoutData();
                data.exclude = compositeSearch.isVisible();
                compositeSearch.setVisible(!compositeSearch.isVisible());
                mainComposite.layout(true);
            }
        };
        searchAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/search.png"))));
        searchAction.setEnabled(true);
        searchAction.setChecked(false);

        limitLogCharsAction = new Action("Limit log chars to " + limitLogChars) {
            public void run() {
                /* OPEN THE DIALOG TO SET THE LIMIT LOG CHARS */
                LimitCharsLogsPreferenceDialog dialog = new LimitCharsLogsPreferenceDialog(
                        Display.getDefault().getActiveShell(), limitLogChars);
                int result = dialog.open();
                if (result == SWT.OK) {
                    limitLogChars = dialog.getLimitLogsChars();
                    limitLogCharsAction.setText("Limit log chars to " + limitLogChars);
                    saveState(memento);
                }
            }
        };
        limitLogCharsAction.setEnabled(true);

        settingsEngine = new Action("Configure Log level") {
            public void run() {
                EnginePreferenceDialog dialog = new EnginePreferenceDialog(Display.getDefault().getActiveShell());
                dialog.open();
            }
        };
        settingsEngine.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/configure_log_level.png"))));
        settingsEngine.setEnabled(true);

        restoreDefaultsAction = new Action("Restore to default parameters") {
            public void run() {
                // Stop the current log thread
                logViewThread = null;

                // clean filter
                filterText.setText("");
                setLogFilter();

                clearLogs();

                // Clean all columns definition
                columnInfos = EngineLogView.clone(DEFAULT_COLUMN_INFOS);
                columnOrder = DEFAULT_COLUMN_ORDER.clone();

                compositeTableViewer.dispose();
                createTableViewer(mainComposite);

                mainComposite.layout(true);

                createLogViewThread();
            }
        };
        restoreDefaultsAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/restore_defaults.png"))));
        restoreDefaultsAction.setEnabled(true);

        clearLogsAction = new Action("Clear log viewer") {
            public void run() {
                logManager.setDateStart(new Date());
                clearLogs();
            }
        };
        clearLogsAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/clear_logs.png"))));

        selectColumnsAction = new Action("Select columns") {
            public void run() {
                Menu selectColumnsMenu = new Menu(mainComposite);

                int i = 0;
                for (ColumnInfo columnInfo : columnInfos) {
                    String columnName = columnInfo.getName();
                    MenuItem item = new MenuItem(selectColumnsMenu, SWT.CHECK);
                    item.setText(columnName);
                    item.setSelection(columnInfo.isVisible());

                    final int _i = i;
                    final ColumnInfo _columnInfo = columnInfo;

                    item.addSelectionListener(new SelectionAdapter() {
                        public void widgetSelected(SelectionEvent e) {
                            boolean bVisible = ((MenuItem) e.widget).getSelection();
                            _columnInfo.setVisible(bVisible);

                            TableColumn column = tableViewer.getTable().getColumn(_i);
                            column.setResizable(bVisible);
                            column.setMoveable(bVisible);
                            column.setWidth(bVisible ? _columnInfo.getSize() : 0);
                        };
                    });
                    i++;
                }

                selectColumnsMenu.setVisible(true);
            }
        };
        selectColumnsAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/select_columns.png"))));

        activateOnNewEventsAction = new RetargetAction("Toggle", "Activate on new events", IAction.AS_CHECK_BOX) {
            public void runWithEvent(Event event) {
                activateOnNewEvents = !activateOnNewEvents;
            }
        };
        activateOnNewEventsAction.setChecked(activateOnNewEvents);
        activateOnNewEventsAction.setEnabled(true);

        scrollLockAction = new RetargetAction("Toggle", "Scroll lock", IAction.AS_CHECK_BOX) {
            public void runWithEvent(Event event) {
                scrollLock = !scrollLock;
            }
        };
        scrollLockAction.setImageDescriptor(ImageDescriptor.createFromImage(
                new Image(Display.getDefault(), getClass().getResourceAsStream("images/scroll_lock.png"))));
        scrollLockAction.setChecked(scrollLock);
        scrollLockAction.setEnabled(true);
    }

    private void clearLogs() {
        logLines.clear();
        tableViewer.getTable().removeAll();
        lastLogTime = -1;
        counter = 0;
        charMeter = 0;
    }

    private void createToolbar() {
        IToolBarManager manager = getViewSite().getActionBars().getToolBarManager();
        manager.add(settingsEngine);
        manager.add(optionsAction);
        manager.add(searchAction);
        manager.add(clearLogsAction);
        manager.add(scrollLockAction);
    }

    private void createMenu() {
        IMenuManager manager = getViewSite().getActionBars().getMenuManager();
        manager.add(selectColumnsAction);
        manager.add(restoreDefaultsAction);
        manager.add(new Separator());
        manager.add(limitLogCharsAction);
        manager.add(activateOnNewEventsAction);
    }

    private void handleContextualTableViewerMenuSelection(int i) {
        IStructuredSelection selection = (IStructuredSelection) tableViewer.getSelection();
        setFilterText(selection, i);

        if (!compositeOptions.isVisible()) {
            optionsAction.setChecked(true);
            compositeOptions.setVisible(true);
            GridData data = (GridData) compositeOptions.getLayoutData();
            data.exclude = false;

            mainComposite.layout(true);
        }
    }

    private void createContextualTableViewerMenu() {
        tableMenu = new Menu(mainComposite.getShell(), SWT.POP_UP);
        tableViewer.getTable().setMenu(tableMenu);

        // Add "equals" command
        MenuItem item = new MenuItem(tableMenu, SWT.PUSH);
        item.setText("Add \"equals\" command");
        item.setImage(new Image(Display.getDefault(),
                getClass().getResourceAsStream("images/log_ctx_menu_add_equals.png")));
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                handleContextualTableViewerMenuSelection(0);
            }
        });

        // Add "contains" command
        item = new MenuItem(tableMenu, SWT.PUSH);
        item.setText("Add \"contains\" command");
        item.setImage(new Image(Display.getDefault(),
                getClass().getResourceAsStream("images/log_ctx_menu_add_contains.png")));
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                handleContextualTableViewerMenuSelection(1);
            }
        });

        // Add "start with" command
        new MenuItem(tableMenu, SWT.SEPARATOR);
        item = new MenuItem(tableMenu, SWT.PUSH);
        item.setText("Add \"starts with\" command");
        item.setImage(new Image(Display.getDefault(),
                getClass().getResourceAsStream("images/log_ctx_menu_add_startswith.png")));
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                handleContextualTableViewerMenuSelection(2);
            }
        });

        // Add "end with" command
        item = new MenuItem(tableMenu, SWT.PUSH);
        item.setText("Add \"ends with\" command");
        item.setImage(new Image(Display.getDefault(),
                getClass().getResourceAsStream("images/log_ctx_menu_add_endswith.png")));
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                handleContextualTableViewerMenuSelection(3);
            }
        });

        // Add variable command
        new MenuItem(tableMenu, SWT.SEPARATOR);
        addVariableItem = new MenuItem(tableMenu, SWT.PUSH);
        addVariableItem.setText("Add variable");
        addVariableItem.setImage(new Image(Display.getDefault(),
                getClass().getResourceAsStream("images/log_ctx_menu_add_variable.png")));
        addVariableItem.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                handleContextualTableViewerMenuSelection(4);
            }
        });
        addVariableItem.setEnabled(false);

        // Add "clear log" command
        new MenuItem(tableMenu, SWT.SEPARATOR);
        item = new MenuItem(tableMenu, SWT.PUSH);
        item.setText("Clear logs");
        item.setImage(new Image(Display.getDefault(), getClass().getResourceAsStream("images/clear_logs.png")));
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                clearLogsAction.run();
            }
        });
    }

    private String getSelectedCellText(String columnName, LogLine line) {

        Class<LogLine> logLineClass = GenericUtils.cast(line.getClass());

        Object result;
        try {
            Method getMethod = logLineClass.getMethod("get" + columnName);
            result = getMethod.invoke(line);

            // Case of empty cell
            if (result == null)
                result = "";
        } catch (Exception e) {
            return null;
        }

        return (String) result;
    }

    private void setFilterText(IStructuredSelection selection, int buttonIndex) {
        if (!selection.isEmpty()) {
            LogLine logline = (LogLine) selection.getFirstElement();

            String columnName = tableViewer.getTable().getColumn(selectedColumnIndex).getText();

            String cellValue = getSelectedCellText(columnName, logline);

            if (cellValue == null)
                return;

            String variableName = columnName.toLowerCase();

            String filter = filterText.getText();
            String txt;
            if (filter.contains("==") || filter.contains("contains") || filter.contains("startsWith")
                    || filter.contains("endsWith")) {
                filter = filter + " and ";
            }
            switch (buttonIndex) {
            case 0:
                txt = filter + "(" + variableName + " == \"" + cellValue.replaceAll("\"", "\\\\\"") + "\")";
                filterText.setText(txt);
                break;
            case 1:
                txt = filter + "(" + variableName + ".contains(\"" + cellValue.replaceAll("\"", "\\\\\"") + "\"))";
                filterText.setText(txt);
                break;
            case 2:
                txt = filter + "(" + variableName + ".startsWith(\"" + cellValue.replaceAll("\"", "\\\\\"")
                        + "\"))";
                filterText.setText(txt);
                break;
            case 3:
                txt = filter + "(" + variableName + ".endsWith(\"" + cellValue.replaceAll("\"", "\\\\\"") + "\"))";
                filterText.setText(txt);
                break;
            case 4:
                // Add variable
                txt = filter + "(" + variableName + " == \""
                        + ((cellValue != null && cellValue != "") ? cellValue.replaceAll("\"", "\\\\\"")
                                : "undefined")
                        + "\")";
                filterText.setText(txt);
                break;
            default:
                break;
            }
        }
    }

    private void createColumns() {
        Table table = tableViewer.getTable();

        while (table.getColumnCount() > 0) {
            table.getColumns()[0].dispose();
        }

        for (ColumnInfo columnInfo : columnInfos) {
            createTableViewerColumn(columnInfo);
        }

        // Set the column saved order
        table.setColumnOrder(columnOrder);
    }

    private TableViewerColumn createTableViewerColumn(ColumnInfo columnInfo) {
        TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.VIRTUAL);
        TableColumn column = viewerColumn.getColumn();
        final String columnName = columnInfo.getName();
        column.setText(columnName);

        column.setResizable(columnInfo.isVisible());
        column.setMoveable(columnInfo.isVisible());
        column.setWidth(columnInfo.isVisible() ? columnInfo.getSize() : 0);

        column.addControlListener(new ControlListener() {
            public void controlResized(ControlEvent event) {
                // Get the current column info
                ColumnInfo columnInfo = getColumnInfo(columnName);

                TableColumn tableColumn = (TableColumn) event.getSource();
                columnInfo.setSize(tableColumn.getWidth());
            }

            public void controlMoved(ControlEvent arg0) {
                int[] newColumnOrder = tableViewer.getTable().getColumnOrder();
                if (newColumnOrder.length == columnOrder.length) {
                    columnOrder = newColumnOrder;
                }
            }
        });

        return viewerColumn;
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    @Override
    public void setFocus() {
        tableViewer.getControl().setFocus();
    }

    private void createLogViewThread() {
        final ViewPart engineLogView = this;

        logViewThread = new Thread(new Runnable() {
            public void run() {
                try {
                    // Wait for the Convertigo engine fully starts the log
                    // objects
                    while (Engine.logConvertigo == null) {
                        Thread.sleep(100);
                    }

                    // Attach the Log4J appender
                    Engine.logConvertigo.addAppender(appender);

                    logManager = new LogManager();
                    logManager.setContinue(true);
                    logManager.setDateStart(new Date(Engine.startStopDate - 10000));
                    logManager.setMaxLines(MAX_BUFFER_LINES);

                    // Get the newest available lines
                    while (getLogs()) {
                        // Refresh the list view
                        Display.getDefault().asyncExec(new Runnable() {

                            public void run() {
                                try {
                                    while (true && !tableViewer.getTable().isDisposed()) {
                                        LogLine line = logLines.remove();
                                        charMeter += line.getMessage().length();
                                        tableViewer.add(line);
                                    }
                                } catch (NoSuchElementException e) {
                                }

                                if (!tableViewer.getTable().isDisposed()) {
                                    while (limitLogChars > 0 && !tableViewer.getTable().isDisposed()
                                            && charMeter > limitLogChars) {
                                        LogLine line = (LogLine) tableViewer.getElementAt(0);
                                        charMeter -= line.getMessage().length();
                                        tableViewer.getTable().remove(0);

                                    }

                                    if (!scrollLock && !tableViewer.getTable().isDisposed()) {
                                        tableViewer.getTable()
                                                .setTopIndex(tableViewer.getTable().getItemCount() - 1);
                                    }
                                }

                                if (activateOnNewEvents) {
                                    IWorkbenchWindow workbenchWindow = ConvertigoPlugin.getDefault().getWorkbench()
                                            .getActiveWorkbenchWindow();
                                    if (workbenchWindow != null) {
                                        workbenchWindow.getActivePage().bringToTop(engineLogView);
                                    }
                                }
                            }

                        });
                    }
                } catch (InterruptedException e) {
                    ConvertigoPlugin.logException(e, "The engine log viewer thread has been interrupted");
                }
            }
        });
        logViewThread.setDaemon(true);
        logViewThread.setName("EngineLogViewerThread");
        logViewThread.start();
    }

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S");

    private boolean getLogs() {
        try {
            JSONArray logs = logManager.getLines();
            boolean interrupted = false;
            while (logs.length() == 0 && !interrupted && Thread.currentThread() == logViewThread) {
                synchronized (appender) {
                    try {
                        appender.wait(300);
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }

                // Detect if the view has been closed
                if (Thread.currentThread() != logViewThread) {
                    return false;
                }
                logs = logManager.getLines();
            }

            HashMap<String, String> allExtras = new HashMap<String, String>();

            for (int i = 0; i < logs.length(); i++) {
                JSONArray logLine = (JSONArray) logs.get(i);

                String dateTime = logLine.getString(1);
                String[] dateTimeParts = dateTime.split(" ");
                String date = dateTimeParts[0];
                String time = dateTimeParts[1];

                String deltaTime;
                try {
                    long currentDate = DATE_FORMAT.parse(dateTime).getTime();
                    if (lastLogTime < 0) {
                        deltaTime = "--";
                    } else {
                        long delta = currentDate - lastLogTime;
                        lastLogTime = currentDate;
                        if (delta < 1000) {
                            deltaTime = StringUtils.leftPad(delta + " ms", 8);
                        } else if (delta < 10000) {
                            deltaTime = StringUtils.leftPad(Math.floor(delta / 10.0) / 100.0 + " s ", 8);
                        } else {
                            deltaTime = StringUtils.leftPad((int) Math.floor(delta / 1000.0) + " s ", 8);
                        }
                    }
                    lastLogTime = currentDate;
                } catch (ParseException e) {
                    deltaTime = "n/a";
                }

                int len = logLine.length();
                for (int j = 5; j < len; j++) {
                    String extra = logLine.getString(j);
                    int k = extra.indexOf("=");
                    allExtras.put(extra.substring(0, k), extra.substring(k + 1));
                }

                // Build the message lines
                String message = logLine.getString(4);
                String[] messageLines = message.split("\n");
                if (messageLines.length > 1) {
                    boolean firstLine = true;
                    for (String messageLine : messageLines) {
                        logLines.add(new LogLine(logLine.getString(0), date, time, deltaTime, logLine.getString(2),
                                logLine.getString(3), messageLine, !firstLine, counter, logLine.getString(4),
                                allExtras));
                        counter++;
                        firstLine = false;
                    }
                } else {
                    logLines.add(new LogLine(logLine.getString(0), date, time, deltaTime, logLine.getString(2),
                            logLine.getString(3), logLine.getString(4), false, counter, logLine.getString(4),
                            allExtras));
                    counter++;
                }
            }
        } catch (JSONException e) {
            ConvertigoPlugin.logException(e, "Unable to process received Engine logs", false);
        } catch (Exception e) {
            ConvertigoPlugin.logException(e,
                    "Error while loading the Engine logs (" + e.getClass().getCanonicalName() + ")", false);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e1) {
            }
        }
        logManager.setContinue(true);
        return true;
    }
}