/*
* This file is part of the OpenSCADA project
* Copyright (C) 2006-2012 TH4 SYSTEMS GmbH (http://th4-systems.com)
*
* OpenSCADA is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenSCADA 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 Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenSCADA. If not, see
* <http://opensource.org/licenses/lgpl-3.0.html> for a copy of the LGPLv3 License.
*/
package org.openscada.ae.ui.views.views;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.set.WritableSet;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.openscada.ae.Event;
import org.openscada.ae.Query;
import org.openscada.ae.QueryListener;
import org.openscada.ae.QueryState;
import org.openscada.ae.ui.views.Activator;
import org.openscada.ae.ui.views.CustomizableAction;
import org.openscada.ae.ui.views.config.ColumnLabelProviderInformation;
import org.openscada.ae.ui.views.config.ConfigurationHelper;
import org.openscada.ae.ui.views.config.EventHistoryViewConfiguration;
import org.openscada.ae.ui.views.dialog.EventHistorySearchDialog;
import org.openscada.ae.ui.views.dialog.SearchType;
import org.openscada.ae.ui.views.model.DecoratedEvent;
import org.openscada.utils.lang.Pair;
import org.openscada.utils.str.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class EventHistoryView extends AbstractAlarmsEventsView
{
private final static Logger logger = LoggerFactory.getLogger ( EventHistoryView.class );
public static final String ID = "org.openscada.ae.ui.views.views.eventhistory"; //$NON-NLS-1$
private static final int LOAD_NO_OF_ITEMS = 2000;
private CustomizableAction clearAction;
private CustomizableAction searchAction;
private CustomizableAction resumeAction;
private EventViewTable eventsTable;
private Pair<SearchType, String> currentFilter;
private final AtomicReference<Query> queryRef = new AtomicReference<Query> ( null );
private final AtomicReference<QueryState> queryState = new AtomicReference<QueryState> ( QueryState.DISCONNECTED );
private final AtomicReference<Throwable> queryError = new AtomicReference<Throwable> ( null );
private ScheduledExecutorService scheduler;
private final AtomicInteger noOfEvents = new AtomicInteger ( 0 );
private final AtomicBoolean isPaused = new AtomicBoolean ( false );
private WritableSet events;
private List<ColumnProperties> initialColumnSettings;
private final Gson gson = new GsonBuilder ().create ();
private List<ColumnLabelProviderInformation> columnInformations;
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
@Override
public void createPartControl ( final Composite parent )
{
super.createPartControl ( parent );
this.scheduler = Executors.newSingleThreadScheduledExecutor ();
// resume Action
this.resumeAction = new CustomizableAction ();
this.resumeAction.setText ( Messages.EventHistoryView_Action_Resume_Text );
this.resumeAction.setToolTipText ( Messages.EventHistoryView_Action_Resume_ToolTop );
this.resumeAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/resume.gif" ) ) ); //$NON-NLS-1$
this.resumeAction.setDisabledImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/resume_disabled.gif" ) ) ); //$NON-NLS-1$
this.resumeAction.setEnabled ( false );
this.resumeAction.setRunnable ( new Runnable () {
@Override
public void run ()
{
resumeEventsRetrieval ();
}
} );
// clear Action
this.clearAction = new CustomizableAction ();
this.clearAction.setText ( Messages.EventHistoryView_Action_Clear_Text );
this.clearAction.setToolTipText ( Messages.EventHistoryView_Action_Clear_ToolTip );
this.clearAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/clear_search.gif" ) ) ); //$NON-NLS-1$
this.clearAction.setEnabled ( false );
this.clearAction.setRunnable ( new Runnable () {
@Override
public void run ()
{
clearData ();
EventHistoryView.this.searchAction.setChecked ( false );
}
} );
// search Action
this.searchAction = new CustomizableAction ( "", IAction.AS_CHECK_BOX );
this.searchAction.setText ( Messages.EventHistoryView_Action_Search_Text );
this.searchAction.setToolTipText ( Messages.EventHistoryView_Action_Search_ToolTip );
this.searchAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/search.gif" ) ) ); //$NON-NLS-1$
this.searchAction.setDisabledImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/search_disabled.gif" ) ) ); //$NON-NLS-1$
this.searchAction.setEnabled ( false );
this.searchAction.setRunnable ( new Runnable () {
@Override
public void run ()
{
EventHistoryView.this.searchAction.setChecked ( true );
pauseEventsRetrieval ();
startEventsRetrieval ();
}
} );
final IToolBarManager toolBarManager = getViewSite ().getActionBars ().getToolBarManager ();
toolBarManager.add ( this.resumeAction );
toolBarManager.add ( this.searchAction );
toolBarManager.add ( this.clearAction );
// label which contains no of retrieved events
this.events = new WritableSet ( SWTObservables.getRealm ( parent.getDisplay () ) );
// load configuration first, since we need the additional columns later
loadConfiguration ();
this.eventsTable = new EventViewTable ( getContentPane (), getViewSite (), SWT.BORDER, this.events, this.initialColumnSettings, this.columnInformations );
this.eventsTable.setLayoutData ( new GridData ( SWT.FILL, SWT.FILL, true, true, 1, 1 ) );
getSite ().setSelectionProvider ( this.eventsTable.getTableViewer () );
}
private void loadConfiguration ()
{
final EventHistoryViewConfiguration cfg = ConfigurationHelper.findEventHistoryViewConfiguration ( getViewSite ().getSecondaryId () );
if ( cfg != null )
{
try
{
setConfiguration ( cfg );
}
catch ( final Exception e )
{
logger.warn ( "Failed to apply configuration", e ); //$NON-NLS-1$
}
}
else
{
logger.info ( "no configuration found" ); //$NON-NLS-1$
}
}
protected void setConfiguration ( final EventHistoryViewConfiguration cfg ) throws Exception
{
switch ( cfg.getConnectionType () )
{
case URI:
setConnectionUri ( cfg.getConnectionString () );
break;
case ID:
setConnectionId ( cfg.getConnectionString () );
break;
}
if ( cfg.getLabel () != null )
{
setPartName ( cfg.getLabel () );
}
this.columnInformations = cfg.getColumnInformation ();
}
/**
* Passing the focus request to the viewer's control.
*/
@Override
public void setFocus ()
{
this.eventsTable.setFocus ();
}
@Override
protected Realm getRealm ()
{
if ( this.events != null )
{
return this.events.getRealm ();
}
return SWTObservables.getRealm ( getSite ().getShell ().getDisplay () );
}
@Override
protected void onConnect ()
{
super.onConnect ();
getSite ().getShell ().getDisplay ().asyncExec ( new Runnable () {
@Override
public void run ()
{
EventHistoryView.this.resumeAction.setEnabled ( false );
EventHistoryView.this.clearAction.setEnabled ( true );
EventHistoryView.this.searchAction.setEnabled ( true );
}
} );
}
@Override
protected void onDisconnect ()
{
super.onDisconnect ();
try
{
getSite ().getShell ().getDisplay ().asyncExec ( new Runnable () {
@Override
public void run ()
{
clearData ();
EventHistoryView.this.resumeAction.setEnabled ( false );
EventHistoryView.this.clearAction.setEnabled ( false );
EventHistoryView.this.searchAction.setEnabled ( false );
}
} );
}
catch ( final Exception e )
{
}
}
/**
* only to be called from GUI thread
*/
private void clearData ()
{
cancelQuery ();
this.noOfEvents.set ( 0 );
this.eventsTable.clear ();
this.resumeAction.setEnabled ( false );
this.clearAction.setEnabled ( true );
this.searchAction.setEnabled ( true );
updateStatusBar ();
}
/**
* only to be called from GUI thread
*/
private void startEventsRetrieval ()
{
final Pair<SearchType, String> filter = EventHistorySearchDialog.open ( getSite ().getShell (), this.currentFilter );
if ( filter != null )
{
this.currentFilter = filter;
retrieveData ( filter.second );
this.resumeAction.setEnabled ( false );
}
}
/**
* only to be called from GUI thread
*/
private void pauseEventsRetrieval ()
{
this.isPaused.set ( true );
if ( this.queryRef.get () != null )
{
this.resumeAction.setEnabled ( true );
}
}
/**
* only to be called from GUI thread
*/
private void resumeEventsRetrieval ()
{
this.isPaused.set ( false );
continueLoading ();
this.resumeAction.setEnabled ( false );
}
private void retrieveData ( final String filter )
{
final QueryListener queryListener = new QueryListener () {
@Override
public void queryStateChanged ( final QueryState state, final Throwable error )
{
EventHistoryView.this.queryError.set ( error );
EventHistoryView.this.queryState.set ( state );
if ( state == QueryState.CONNECTED && !EventHistoryView.this.isPaused.get () )
{
EventHistoryView.this.resumeAction.setEnabled ( true );
}
else if ( state == QueryState.DISCONNECTED )
{
EventHistoryView.this.queryRef.set ( null );
getSite ().getShell ().getDisplay ().asyncExec ( new Runnable () {
@Override
public void run ()
{
}
} );
}
updateStatusBar ();
}
@Override
public void queryData ( final Event[] events )
{
final Set<DecoratedEvent> decoratedEvents;
if ( events == null )
{
decoratedEvents = new LinkedHashSet<DecoratedEvent> ();
}
else
{
// set initial capacity to expected capacity
// which should be a bit more efficient, because the space
// for the given events has to be reserved anyway
decoratedEvents = new LinkedHashSet<DecoratedEvent> ( events.length + 1 );
for ( final Event event : events )
{
decoratedEvents.add ( new DecoratedEvent ( event ) );
}
}
EventHistoryView.this.noOfEvents.addAndGet ( decoratedEvents.size () );
getSite ().getShell ().getDisplay ().asyncExec ( new Runnable () {
@Override
public void run ()
{
updateStatusBar ();
for ( final DecoratedEvent decoratedEvent : decoratedEvents )
{
EventHistoryView.this.events.add ( decoratedEvent );
}
}
} );
}
};
clearData ();
if ( isConnected () )
{
this.isPaused.set ( false );
this.queryRef.set ( getConnection ().createQuery ( "client", filter, queryListener ) ); //$NON-NLS-1$
}
}
private void cancelQuery ()
{
this.isPaused.set ( true );
if ( this.queryRef.get () != null )
{
this.queryRef.get ().close ();
}
this.queryRef.set ( null );
}
private void continueLoading ()
{
this.scheduler.schedule ( new Runnable () {
@Override
public void run ()
{
if ( EventHistoryView.this.queryRef.get () != null )
{
EventHistoryView.this.queryRef.get ().loadMore ( LOAD_NO_OF_ITEMS );
}
}
}, 50, TimeUnit.MILLISECONDS );
}
@Override
protected void updateStatusBar ()
{
scheduleJob ( new Runnable () {
@Override
public void run ()
{
EventHistoryView.this.getStateLabel ().setText ( createStatusLabel () );
final Throwable error = EventHistoryView.this.queryError.get ();
getStateLabel ().setToolTipText ( error != null ? error.getMessage () : null );
}
} );
}
protected String createStatusLabel ()
{
final List<String> labels = new LinkedList<String> ();
labels.add ( getLabelForConnection () );
if ( this.currentFilter != null )
{
labels.add ( String.format ( Messages.EventHistoryView_Label_Format_Filter, this.currentFilter.second.replace ( "&", "&&" ) ) ); //$NON-NLS-2$
}
if ( this.queryState.get () == QueryState.LOADING )
{
labels.add ( Messages.EventHistoryView_Label_Format_IsLoading );
}
labels.add ( String.format ( Messages.EventHistoryView_Label_Format_CountEvents, EventHistoryView.this.events.size () ) );
return StringHelper.join ( labels, Messages.EventHistoryView_Sep );
}
@Override
protected void watchPool ( final String poolId )
{
// pass
}
@Override
protected void watchMonitors ( final String monitorsId )
{
// pass
}
@Override
public void init ( final IViewSite site, final IMemento memento ) throws PartInitException
{
super.init ( site, memento );
if ( memento != null )
{
final String s = memento.getString ( "columnSettings" ); //$NON-NLS-1$
if ( s != null )
{
this.initialColumnSettings = this.gson.fromJson ( s, new TypeToken<List<ColumnProperties>> () {}.getType () );
}
}
}
@Override
public void saveState ( final IMemento memento )
{
memento.putString ( "columnSettings", this.gson.toJson ( this.eventsTable.getColumnSettings () ) ); //$NON-NLS-1$
super.saveState ( memento );
}
}