/* * 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.set.WritableSet; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; 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.custom.BusyIndicator; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.IMemento; import org.eclipse.ui.IViewSite; import org.eclipse.ui.PartInitException; import org.openscada.ae.Event; import org.openscada.ae.Event.Fields; import org.openscada.ae.MonitorStatusInformation; import org.openscada.ae.client.EventListener; 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.EventPoolViewConfiguration; 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.ae.ui.views.model.DecoratedMonitor; import org.openscada.ae.ui.views.model.MonitorData; import org.openscada.ae.ui.views.preferences.PreferenceConstants; import org.openscada.core.Variant; import org.openscada.core.subscription.SubscriptionState; import org.openscada.utils.concurrent.NamedThreadFactory; 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 EventPoolView extends MonitorSubscriptionAlarmsEventsView { private static final Logger logger = LoggerFactory.getLogger ( EventPoolView.class ); public static final String ID = "org.openscada.ae.ui.views.views.eventpool"; //$NON-NLS-1$ private String poolId; private WritableSet pool; private EventListener eventPoolListener; private final Map<String, Set<DecoratedEvent>> poolMap = new HashMap<String, Set<DecoratedEvent>> (); private EventViewTable eventsTable; private List<ColumnProperties> initialColumnSettings; private final Gson gson = new GsonBuilder ().create (); private final Object jobLock = new Object (); private Collection<Event> eventList; private ScheduledExecutorService scheduler; /** * The maximum number of event that will be kept when * cleaning up the event list */ private int maxNumberOfEvents = 0; /** * The total maximum when the scroll lock will be overridden and events * are removed down to {@link #maxNumberOfEvents} */ private int forceEventLimit = 0; private List<ColumnLabelProviderInformation> columnInformation; public String getPoolId () { return this.poolId; } public void setPoolId ( final String poolId ) { if ( poolId == null ) { unSubscribe (); this.poolId = null; return; } if ( !String.valueOf ( poolId ).equals ( String.valueOf ( this.poolId ) ) ) { unSubscribe (); this.poolId = poolId; subscribe (); } } /** * 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 ( new NamedThreadFactory ( "shortenEventPool" ) ); //$NON-NLS-1$ final int shortenEverySeconds = Activator.getDefault ().getPreferenceStore ().getInt ( PreferenceConstants.CUT_LIST_ALL_SECONDS_KEY ); this.scheduler.scheduleAtFixedRate ( new Runnable () { @Override public void run () { scheduleJob ( new Runnable () { @Override public void run () { removeEvents (); updateStatusBar (); } } ); } }, shortenEverySeconds, shortenEverySeconds, TimeUnit.SECONDS ); this.pool = new WritableSet ( SWTObservables.getRealm ( parent.getDisplay () ) ); this.pool.addChangeListener ( new IChangeListener () { @Override public void handleChange ( final ChangeEvent event ) { updateStatusBar (); } } ); /* final CustomizableAction commentAction = createCommentAction ( null ); commentAction.setRunnable ( new Runnable () { @Override public void run () { if ( EventPoolView.this.eventsTable.selectedEvents ().size () == 0 ) { return; } final DecoratedEvent event = EventPoolView.this.eventsTable.selectedEvents ().get ( 0 ); Variant comment = event.getEvent ().getField ( Fields.COMMENT ); final InputDialog dlg = new InputDialog ( parent.getShell (), commentAction.getText (), commentAction.getDescription (), comment == null ? "" : comment.asString ( "" ), null ); //$NON-NLS-1$ //$NON-NLS-2$ if ( dlg.open () == Window.OK ) { comment = Variant.valueOf ( dlg.getValue () ); for ( final DecoratedEvent decoratedEvent : EventPoolView.this.eventsTable.selectedEvents () ) { final Event updatedEvent = Event.create ().event ( decoratedEvent.getEvent () ).attribute ( Fields.COMMENT, comment ).build (); // FIXME: implement "set comment" in client interface logger.info ( "comment updated " + updatedEvent ); //$NON-NLS-1$ } } } } ); */ final CustomizableAction scrollLockAction = new CustomizableAction ( Messages.EventPoolView_Action_ScrollLock_Name, IAction.AS_CHECK_BOX ); scrollLockAction.setToolTipText ( Messages.EventPoolView_Action_ScrollLock_ToolTip ); scrollLockAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/scroll_lock.gif" ) ) ); //$NON-NLS-1$ scrollLockAction.setRunnable ( new Runnable () { @Override public void run () { EventPoolView.this.eventsTable.setScrollLock ( scrollLockAction.isChecked () ); } } ); final CustomizableAction setFilterAction = new CustomizableAction ( "", IAction.AS_CHECK_BOX ); //$NON-NLS-1$ setFilterAction.setText ( Messages.EventPoolView_Action_Filter_Name ); setFilterAction.setToolTipText ( Messages.EventPoolView_Action_Filter_ToolTip ); setFilterAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/search.gif" ) ) ); //$NON-NLS-1$ setFilterAction.setRunnable ( new Runnable () { @Override public void run () { // we need to open the dialog outside of the busy loop setFilterAction.setChecked ( true ); final Pair<SearchType, String> result = EventHistorySearchDialog.open ( parent.getShell (), EventPoolView.this.eventsTable.getFilter () ); BusyIndicator.showWhile ( parent.getDisplay (), new Runnable () { @Override public void run () { EventPoolView.this.eventsTable.setFilter ( result ); setFilterAction.setChecked ( EventPoolView.this.eventsTable.getFilter () != null ); updateStatusBar (); } } ); } } ); final CustomizableAction removeFilterAction = new CustomizableAction (); removeFilterAction.setText ( Messages.EventPoolView_Action_RemoveFilter_Name ); removeFilterAction.setToolTipText ( Messages.EventPoolView_Action_RemoveFilter_ToolTip ); removeFilterAction.setImageDescriptor ( ImageDescriptor.createFromURL ( Activator.getDefault ().getBundle ().getResource ( "icons/clear_search.gif" ) ) ); //$NON-NLS-1$ removeFilterAction.setRunnable ( new Runnable () { @Override public void run () { BusyIndicator.showWhile ( parent.getDisplay (), new Runnable () { @Override public void run () { EventPoolView.this.eventsTable.removeFilter (); setFilterAction.setChecked ( false ); updateStatusBar (); } } ); } } ); final IToolBarManager toolBarManager = getViewSite ().getActionBars ().getToolBarManager (); toolBarManager.add ( scrollLockAction ); toolBarManager.add ( setFilterAction ); toolBarManager.add ( removeFilterAction ); loadConfiguration (); this.eventsTable = new EventViewTable ( getContentPane (), getViewSite (), SWT.BORDER, this.pool, this.initialColumnSettings, this.columnInformation ); this.eventsTable.setLayoutData ( new GridData ( SWT.FILL, SWT.FILL, true, true, 1, 1 ) ); getSite ().setSelectionProvider ( this.eventsTable.getTableViewer () ); } private void loadConfiguration () { final EventPoolViewConfiguration cfg = ConfigurationHelper.findEventPoolViewConfiguration ( 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 EventPoolViewConfiguration cfg ) throws Exception { setPoolId ( cfg.getEventPoolQueryId () ); setMonitorsId ( cfg.getMonitorQueryId () ); switch ( cfg.getConnectionType () ) { case URI: setConnectionUri ( cfg.getConnectionString () ); break; case ID: setConnectionId ( cfg.getConnectionString () ); break; } if ( cfg.getLabel () != null ) { setPartName ( cfg.getLabel () ); } int maxNumberOfEvents = cfg.getMaxNumberOfEvents (); if ( Activator.getDefault ().getPreferenceStore ().getInt ( PreferenceConstants.NUMBER_OF_EVENTS_KEY ) > 0 ) { maxNumberOfEvents = Activator.getDefault ().getPreferenceStore ().getInt ( PreferenceConstants.NUMBER_OF_EVENTS_KEY ); } this.maxNumberOfEvents = maxNumberOfEvents; this.forceEventLimit = cfg.getForceEventLimit (); this.columnInformation = cfg.getColumnInformation (); } /** * Passing the focus request to the viewer's control. */ @Override public void setFocus () { this.eventsTable.setFocus (); } @Override protected void subscribe () { super.subscribe (); if ( getConnection () != null && this.poolId != null ) { this.eventPoolListener = new EventListener () { @Override public void statusChanged ( final SubscriptionState state ) { EventPoolView.this.statusChangedEventSubscription ( state ); } @Override public void dataChanged ( final Event[] addedEvents ) { EventPoolView.this.dataChanged ( addedEvents ); } }; getConnectionService ().getEventManager ().addEventListener ( this.poolId, this.eventPoolListener ); } } @Override protected void unSubscribe () { super.unSubscribe (); if ( getConnection () != null && this.poolId != null ) { if ( this.eventPoolListener != null ) { getConnectionService ().getEventManager ().removeEventListener ( this.poolId, this.eventPoolListener ); } } clear (); } protected void dataChanged ( final Event[] addedEvents ) { synchronized ( this.jobLock ) { boolean created = false; if ( this.eventList == null ) { this.eventList = new ArrayList<Event> (); created = true; } this.eventList.addAll ( Arrays.asList ( addedEvents ) ); if ( created ) { getRealm ().asyncExec ( new Runnable () { @Override public void run () { getRealm ().timerExec ( 1000, new Runnable () { @Override public void run () { processEvents (); } } ); } } ); } } } protected void processEvents () { final Collection<Event> list; synchronized ( this.jobLock ) { list = this.eventList; this.eventList = null; } if ( list != null ) { performDataChanged ( list ); } } private void performDataChanged ( final Collection<Event> addedEvents ) { if ( addedEvents == null || addedEvents.isEmpty () ) { return; } final Set<DecoratedEvent> decoratedEvents = decorateEvents ( addedEvents ); for ( final DecoratedEvent event : decoratedEvents ) { final Variant source = event.getEvent ().getField ( Fields.SOURCE ); if ( source != null && !source.isNull () && source.asString ( "" ).length () > 0 ) //$NON-NLS-1$ { final String str = source.asString ( "" ); //$NON-NLS-1$ Set<DecoratedEvent> d = EventPoolView.this.poolMap.get ( str ); if ( d == null ) { d = new HashSet<DecoratedEvent> (); EventPoolView.this.poolMap.put ( str, d ); } d.add ( event ); } } // adding more events costs more time if ( addedEvents.size () > 10 ) { BusyIndicator.showWhile ( getSite ().getShell ().getDisplay (), new Runnable () { @Override public void run () { insertElements ( decoratedEvents ); } } ); } else { insertElements ( decoratedEvents ); } } private void insertElements ( final Set<DecoratedEvent> decoratedEvents ) { final Table table = this.eventsTable.getTableViewer ().getTable (); TableItem item = null; try { item = table.getItem ( table.getTopIndex () ); } catch ( final IllegalArgumentException e ) { // ignore } this.pool.addAll ( decoratedEvents ); if ( item != null ) { try { if ( this.eventsTable.isScrollLock () ) { table.setTopIndex ( table.indexOf ( item ) ); } } catch ( final IllegalArgumentException e ) { // ignore } } } private void removeEvents () { if ( this.maxNumberOfEvents <= 0 ) { return; } if ( this.eventsTable.isScrollLock () && ( this.forceEventLimit <= 0 || this.pool.size () < this.forceEventLimit ) ) { return; } try { @SuppressWarnings ( "unchecked" ) final List<DecoratedEvent> tmpList = new ArrayList<DecoratedEvent> ( EventPoolView.this.pool ); final List<DecoratedEvent> toRemove = new ArrayList<DecoratedEvent> (); Collections.sort ( tmpList, new Comparator<DecoratedEvent> () { @Override public int compare ( final DecoratedEvent e1, final DecoratedEvent e2 ) { return e2.compareTo ( e1 ); } } ); int i = 0; for ( final DecoratedEvent event : tmpList ) { if ( i > this.maxNumberOfEvents ) { toRemove.add ( event ); } i++; } EventPoolView.this.pool.removeAll ( toRemove ); tmpList.clear (); toRemove.clear (); } catch ( final Throwable th ) { Activator.getDefault ().getLog ().log ( new Status ( IStatus.ERROR, Activator.PLUGIN_ID, 42, Messages.EventPoolView_Status_Error_RemoveElement, th ) ); } } @Override public void dataChanged ( final MonitorStatusInformation[] addedOrUpdated, final String[] removed ) { super.dataChanged ( addedOrUpdated, removed ); scheduleJob ( new Runnable () { @Override public void run () { performDataChanged ( addedOrUpdated, removed ); } } ); } private void performDataChanged ( final MonitorStatusInformation[] addedOrUpdated, final String[] removed ) { // FIXME: check if addAll is really necessary EventPoolView.this.pool.addAll ( decorateEvents ( addedOrUpdated, removed ) ); } private Set<DecoratedEvent> decorateEvents ( final MonitorStatusInformation[] monitors, final String[] removed ) { final Set<DecoratedEvent> result = new HashSet<DecoratedEvent> (); if ( monitors != null ) { for ( final MonitorStatusInformation monitorStatusInformation : monitors ) { final Set<DecoratedEvent> d = this.poolMap.get ( monitorStatusInformation.getId () ); if ( d != null ) { for ( final DecoratedEvent event : d ) { event.setMonitor ( new MonitorData ( monitorStatusInformation ) ); result.add ( event ); } } } } if ( removed != null ) { for ( final String monitorId : removed ) { final Set<DecoratedEvent> d = this.poolMap.get ( monitorId ); if ( d != null ) { for ( final DecoratedEvent event : d ) { event.setMonitor ( null ); result.add ( event ); } } } } return result; } private Set<DecoratedEvent> decorateEvents ( final Collection<Event> events ) { final Set<DecoratedEvent> result = new HashSet<DecoratedEvent> (); for ( final Event event : events ) { final Variant source = event.getField ( Fields.SOURCE ); final MonitorData monitor; if ( source != null && !source.isNull () && source.isString () ) { final DecoratedMonitor decoratedMonitor = (DecoratedMonitor)this.monitorsMap.get ( source.asString ( "" ) ); //$NON-NLS-1$ if ( decoratedMonitor != null ) { monitor = decoratedMonitor.getMonitor (); } else { monitor = null; } } else { monitor = null; } result.add ( new DecoratedEvent ( event, monitor ) ); } return result; } private void clear () { this.pool.getRealm ().asyncExec ( new Runnable () { @Override public void run () { if ( EventPoolView.this.pool != null ) { EventPoolView.this.pool.clear (); } } } ); } private void statusChangedEventSubscription ( final SubscriptionState state ) { } @Override protected void watchPool ( final String poolId ) { setPoolId ( poolId ); } @Override protected void watchMonitors ( final String monitorsId ) { setMonitorsId ( monitorsId ); } @Override protected void updateStatusBar () { scheduleJob ( new Runnable () { @Override public void run () { EventPoolView.this.getStateLabel ().setText ( createStatusLabel () ); } } ); } protected String createStatusLabel () { final List<String> labels = new LinkedList<String> (); labels.add ( getLabelForConnection () ); if ( this.poolId != null ) { labels.add ( String.format ( Messages.EventPoolView_Label_Format_Pool, this.poolId ) ); } else { labels.add ( Messages.EventPoolView_Label_Format_NoPool ); } if ( this.monitorsId != null ) { labels.add ( String.format ( Messages.EventPoolView_Label_Format_Monitors, this.monitorsId ) ); } else { labels.add ( Messages.EventPoolView_Label_Format_NoMonitors ); } labels.add ( String.format ( Messages.EventPoolView_Label_Format_CountEvents, EventPoolView.this.pool.size () ) ); final Pair<SearchType, String> filter = this.eventsTable.getFilter (); if ( filter != null && filter.second != null && !filter.second.isEmpty () ) { labels.add ( String.format ( Messages.EventPoolView_Label_Format_Filter, filter.second ).replace ( "&", "&&" ) ); //$NON-NLS-2$ } return StringHelper.join ( labels, Messages.EventPoolView_Sep ); } @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 ); } @Override public void dispose () { super.dispose (); this.scheduler.shutdown (); this.scheduler = null; } }