/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.features.poller.remote.gwt.client; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import org.opennms.features.poller.remote.gwt.client.FilterPanel.Filters; import org.opennms.features.poller.remote.gwt.client.FilterPanel.FiltersChangedEvent; import org.opennms.features.poller.remote.gwt.client.FilterPanel.StatusSelectionChangedEvent; import org.opennms.features.poller.remote.gwt.client.TagPanel.TagClearedEvent; import org.opennms.features.poller.remote.gwt.client.TagPanel.TagSelectedEvent; import org.opennms.features.poller.remote.gwt.client.data.AndFilter; import org.opennms.features.poller.remote.gwt.client.data.ApplicationFilter; import org.opennms.features.poller.remote.gwt.client.data.DataManager; import org.opennms.features.poller.remote.gwt.client.data.LocationFilter; import org.opennms.features.poller.remote.gwt.client.data.StatusFilter; import org.opennms.features.poller.remote.gwt.client.data.TagFilter; import org.opennms.features.poller.remote.gwt.client.events.ApplicationDeselectedEvent; import org.opennms.features.poller.remote.gwt.client.events.ApplicationDetailsRetrievedEvent; import org.opennms.features.poller.remote.gwt.client.events.ApplicationSelectedEvent; import org.opennms.features.poller.remote.gwt.client.events.GWTMarkerClickedEvent; import org.opennms.features.poller.remote.gwt.client.events.GWTMarkerInfoWindowRefreshEvent; import org.opennms.features.poller.remote.gwt.client.events.LocationManagerInitializationCompleteEvent; import org.opennms.features.poller.remote.gwt.client.events.LocationManagerInitializationCompleteEventHander; import org.opennms.features.poller.remote.gwt.client.events.LocationPanelSelectEvent; import org.opennms.features.poller.remote.gwt.client.events.LocationsUpdatedEvent; import org.opennms.features.poller.remote.gwt.client.events.MapPanelBoundsChangedEvent; import org.opennms.features.poller.remote.gwt.client.location.LocationDetails; import org.opennms.features.poller.remote.gwt.client.location.LocationInfo; import org.opennms.features.poller.remote.gwt.client.remoteevents.LocationUpdatedRemoteEvent; import org.opennms.features.poller.remote.gwt.client.remoteevents.LocationsUpdatedRemoteEvent; import org.opennms.features.poller.remote.gwt.client.remoteevents.MapRemoteEventHandler; import org.opennms.features.poller.remote.gwt.client.remoteevents.UpdateCompleteRemoteEvent; import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.http.client.URL; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.Label; import de.novanic.eventservice.client.event.RemoteEventService; /** * <p> * This class implements both {@link LocationManager} (the model portion of * the webapp) and {@link RemotePollerPresenter} (the controller portion of * the webapp code). It is responsible for maintaining the knowledgebase of * {@link Location} objects and responding to events triggered when: * </p> * <ul> * <li>{@link Location} instances are added or m_updated</li> * <li>the UI elements are clicked on by the user</li> * </ul> * <p> * If this class ever grows too large, we can split it into separate model and * controller classes. * </p> * * @author ranger * @version $Id: $ * @since 1.8.1 */ public class DefaultLocationManager implements LocationManager, RemotePollerPresenter { public class ApplicationUpdater implements Command { private ApplicationInfo m_applicationInfo; public ApplicationUpdater(ApplicationInfo applicationInfo) { m_applicationInfo = applicationInfo; } public void schedule(CommandExecutor executor) { executor.schedule(this); } public void execute() { doApplicationUpdate(m_applicationInfo); } } public class LocationUpdater implements Scheduler.RepeatingCommand { private Queue<LocationInfo> m_locations; private int m_count = 0; private int m_updated = 0; public LocationUpdater(Collection<LocationInfo> locations) { m_locations = new LinkedList<LocationInfo>(locations); m_count = m_locations.size(); } public void schedule(CommandExecutor executor) { executor.schedule(this); } public boolean execute() { if(m_locations.isEmpty()) { return false; }else { LocationInfo location = m_locations.remove(); updateLocation(location); m_updated++; m_view.setStatusMessage("Updated " + m_updated + " of " + m_count); return !m_locations.isEmpty(); } } } private final DataManager m_dataManager = new DataManager(); private final ApplicationFilter m_applicationFilter = new ApplicationFilter(); private final StatusFilter m_statusFilter = new StatusFilter(); private final TagFilter m_tagFilter = new TagFilter(); private final LocationFilter m_locationViewFilter = m_tagFilter; private final LocationFilter m_applicationViewFilter = new AndFilter(m_applicationFilter, m_tagFilter); private LocationFilter m_selectedFilter = m_locationViewFilter; private final HandlerManager m_eventBus; private final HandlerManager m_handlerManager = new HandlerManager(this); private final LocationStatusServiceAsync m_remoteService; private final RemoteEventService m_remoteEventService; private boolean m_updated = false; private final ApplicationView m_view; private final CommandExecutor m_executor; private final AndFilter m_selectedVisibleFilter = new AndFilter(m_selectedFilter, m_statusFilter); /** * <p>Constructor for DefaultLocationManager.</p> * * @param eventBus a {@link com.google.gwt.event.shared.HandlerManager} object. * @param view * @param mapPanel a {@link org.opennms.features.poller.remote.gwt.client.MapPanel} object. * @param panel a {@link com.google.gwt.user.client.ui.SplitLayoutPanel} object. * @param locationPanel a {@link org.opennms.features.poller.remote.gwt.client.LocationPanel} object. */ public DefaultLocationManager(final HandlerManager eventBus, ApplicationView view, LocationStatusServiceAsync remoteService, RemoteEventService remoteEventService, CommandExecutor commandExecutor) { m_eventBus = eventBus; m_view = view; m_remoteService = remoteService; m_remoteEventService = remoteEventService; m_executor = commandExecutor; // Register for all relevant events thrown by the UI components m_eventBus.addHandler(LocationPanelSelectEvent.TYPE, this); m_eventBus.addHandler(MapPanelBoundsChangedEvent.TYPE, this); m_eventBus.addHandler(FiltersChangedEvent.TYPE, this); m_eventBus.addHandler(TagSelectedEvent.TYPE, this); m_eventBus.addHandler(TagClearedEvent.TYPE, this); m_eventBus.addHandler(StatusSelectionChangedEvent.TYPE, this); m_eventBus.addHandler(ApplicationDeselectedEvent.TYPE, this); m_eventBus.addHandler(ApplicationSelectedEvent.TYPE, this); m_eventBus.addHandler(GWTMarkerClickedEvent.TYPE, this); m_eventBus.addHandler(GWTMarkerInfoWindowRefreshEvent.TYPE, this); initialize(view.getSelectedStatuses()); } public void initialize(Set<Status> statuses) { m_statusFilter.setStatuses(statuses); initializeEventService(); startStatusEvents(); } private void initializeEventService() { LocationListener locationListener = new DefaultLocationListener(this); final RemoteEventService eventService = getRemoteEventService(); eventService.addListener(MapRemoteEventHandler.LOCATION_EVENT_DOMAIN, locationListener); eventService.addListener(null, locationListener); } private void startStatusEvents() { getRemoteService().start(new AsyncCallback<Void>() { public void onFailure(Throwable throwable) { // Log.debug("unable to start location even service backend", throwable); Window.alert("unable to start location event service backend: " + throwable.getMessage()); throw new InitializationException("remote service start failed", throwable); } public void onSuccess(Void voidArg) { } }); } /** * <p>initializationComplete</p> */ protected void initializationComplete() { m_handlerManager.fireEvent(new LocationManagerInitializationCompleteEvent()); } /** * <p>getRemoteService</p> * * @return a {@link org.opennms.features.poller.remote.gwt.client.LocationStatusServiceAsync} object. */ protected LocationStatusServiceAsync getRemoteService() { return m_remoteService; } /** {@inheritDoc} */ public void addLocationManagerInitializationCompleteEventHandler(LocationManagerInitializationCompleteEventHander handler) { m_handlerManager.addHandler(LocationManagerInitializationCompleteEvent.TYPE, handler); }; /** * <p>displayDialog</p> * * @param title a {@link java.lang.String} object. * @param contents a {@link java.lang.String} object. */ protected void displayDialog(final String title, final String contents) { final DialogBox db = new DialogBox(); db.setAutoHideEnabled(true); db.setModal(true); db.setText(title); db.setWidget(new Label(contents, true)); db.show(); } /** * <p>getAllLocationNames</p> * * @return a {@link java.util.Set} object. */ public Set<String> getAllLocationNames() { return m_dataManager.getAllLocationNames(); } /** {@inheritDoc} */ public void reportError(final String errorMessage, final Throwable throwable) { // FIXME: implement error reporting in UI } /** * <p>fitMapToLocations</p> */ public void fitMapToLocations() { m_view.fitMapToLocations(m_dataManager.getLocationBounds()); } /** * TODO: Figure out if this public function is necessary or if we can get * by just responding to incoming events. * * @return a {@link java.util.ArrayList} object. */ public ArrayList<LocationInfo> getLocationsForLocationPanel() { // Use an ArrayList so that it has good random-access efficiency // since the pageable lists use get() to fetch based on index. final List<LocationInfo> visibleLocations = m_dataManager.getMatchingLocations(m_selectedVisibleFilter); GWTBounds mapBounds = m_view.getMapBounds(); final ArrayList<LocationInfo> inBounds = new ArrayList<LocationInfo>(); for (final LocationInfo location : visibleLocations) { final GWTMarkerState markerState = location.getMarkerState(); if ( markerState.isWithinBounds(mapBounds)) { inBounds.add(location); } } // TODO: this should use the current filter set eventually, for now // sort by priority, then name // for now, LocationInfo is Comparable and has a natural sort ordering // based on status, priority, and name Collections.sort(inBounds, new Comparator<LocationInfo>() { public int compare(LocationInfo o1, LocationInfo o2) { return o1.compareTo(o2); } }); return inBounds; } private void updateAllMarkerStates() { for (final LocationInfo location : m_dataManager.getLocations()) { placeMarker(location); } } /** * {@inheritDoc} * * Handler triggered when a user clicks on a specific location record. */ public void onLocationSelected(final LocationPanelSelectEvent event) { showLocationDetails(event.getLocationName()); } private void showLocationDetails(final String locationName) { // TODO: this needs a callback to get the location details, and fill // in the content final LocationInfo loc = m_dataManager.getLocation(locationName); m_remoteService.getLocationDetails(locationName, new AsyncCallback<LocationDetails>() { public void onFailure(final Throwable t) { String htmlTitle = "Error Getting Location Details"; String htmlContent = "<p>An error occurred getting the location details.</p>" + "<pre>" + URL.encode(t.getMessage()) + "</pre>"; m_view.showLocationDetails(locationName, htmlTitle, htmlContent); } public void onSuccess(final LocationDetails locationDetails) { m_view.showLocationDetails( locationName, locationName + " (" + loc.getArea() + ")", getLocationInfoDetails(loc, locationDetails) ); } }); } /** * {@inheritDoc} * * Refresh the list of locations whenever the map panel boundaries change. */ public void onBoundsChanged(final MapPanelBoundsChangedEvent e) { // make sure each location's marker is up-to-date updateAllMarkerStates(); m_view.setSelectedTag(m_tagFilter.getSelectedTag(), m_dataManager.getAllTags()); m_view.updateLocationList(getLocationsForLocationPanel()); } /** * {@inheritDoc} * * Invoked by the {@link LocationUpdatedRemoteEvent} and * {@link LocationsUpdatedRemoteEvent} events. */ public void updateLocation(final LocationInfo newLocation) { if (newLocation != null) { LocationInfo oldLocation = m_dataManager.getLocation(newLocation.getName()); // Update the location information in the model m_dataManager.updateLocation(newLocation); m_view.updateApplicationNames(m_dataManager.getAllApplicationNames()); placeMarker(newLocation); if (m_updated) { // Update the icon/caption in the LHN if(oldLocation == null || (m_selectedVisibleFilter.matches(newLocation) != m_selectedVisibleFilter.matches(oldLocation)) || (oldLocation.getStatus() != newLocation.getStatus())) { m_view.updateLocationList(getLocationsForLocationPanel()); } m_eventBus.fireEvent(new LocationsUpdatedEvent()); } } } private void placeMarker(final LocationInfo info) { final GWTMarkerState markerState = info.getMarkerState(); markerState.setVisible(m_statusFilter.matches(info)); markerState.setSelected(m_selectedFilter.matches(info)); markerState.place(m_view); } /** * {@inheritDoc} * * Invoked by the {@link org.opennms.features.poller.remote.gwt.client.remoteevents.ApplicationUpdatedRemoteEvent} and * {@link org.opennms.features.poller.remote.gwt.client.remoteevents.ApplicationUpdatedRemoteEvent} events. */ public void updateApplication(final ApplicationInfo applicationInfo) { ApplicationUpdater appUpdater = new ApplicationUpdater(applicationInfo); appUpdater.schedule(m_executor); } private void doApplicationUpdate(final ApplicationInfo applicationInfo) { if (applicationInfo == null) return; // Update the application information in the model m_dataManager.updateApplication(applicationInfo); m_view.updateApplicationNames(m_dataManager.getAllApplicationNames()); /* * Update the icon/caption in the LHN Use an ArrayList so that it has * good random-access efficiency since the pageable lists use get() to * fetch based on index. Note, that m_applications is a *HashSet* not * a *TreeSet* since TreeSets consider duplicates based on Comparable, * not equals, and thus duplicates them. */ m_view.updateApplicationList(m_dataManager.getApplications()); if (!m_updated) { return; } updateAllMarkerStates(); m_eventBus.fireEvent(new LocationsUpdatedEvent()); } /** {@inheritDoc} */ public void removeApplication(final String applicationName) { final ApplicationInfo info = m_dataManager.getApplicationInfo(applicationName); m_dataManager.removeApplication(applicationName); if (info != null) { m_applicationFilter.removeApplication(info); m_view.updateApplicationList(m_dataManager.getApplications()); } m_eventBus.fireEvent(new LocationsUpdatedEvent()); } /** * Invoked by the {@link UpdateCompleteRemoteEvent} event. */ public void updateComplete() { m_dataManager.updateComplete(); if (!m_updated) { updateAllMarkerStates(); fitMapToLocations(); m_updated = true; } } /** {@inheritDoc} */ public void onFiltersChanged(Filters filters) { // TODO: Update state inside of this object to track the filter state // (if necessary) // TODO: Update markers on the map panel // TODO: Update the list of objects in the LHN } /** {@inheritDoc} */ public void onTagSelected(String tagName) { // Update state inside of this object to track the selected tag m_tagFilter.setSelectedTag(tagName); // make sure each location's marker is up-to-date updateAllMarkerStates(); m_view.updateLocationList(getLocationsForLocationPanel()); } /** * <p>onTagCleared</p> */ public void onTagCleared() { // Update state inside of this object to track the selected tag m_tagFilter.setSelectedTag(null); // make sure each location's marker is up-to-date updateAllMarkerStates(); // TODO: Update markers on the map panel // Update the list of objects in the LHN m_view.updateLocationList(getLocationsForLocationPanel()); } /** * Fetch a list of all application names. * * @return a {@link java.util.Set} object. */ public Set<String> getAllApplicationNames() { //TODO: move this to ApplicationPresenter return m_dataManager.getAllApplicationNames(); } /** {@inheritDoc} */ public ApplicationInfo getApplicationInfo(final String name) { return m_dataManager.getApplicationInfo(name); } /** {@inheritDoc} */ public LocationInfo getLocation(String locationName) { return m_dataManager.getLocation(locationName); } /** {@inheritDoc} */ public void onGWTMarkerClicked(GWTMarkerClickedEvent event) { GWTMarkerState markerState = event.getMarkerState(); showLocationDetails(markerState.getName()); } public void onGWTMarkerInfoWindowRefresh(GWTMarkerInfoWindowRefreshEvent event) { refreshLocationInfoWindowDetails(event.getMarkerState().getName()); } private void refreshLocationInfoWindowDetails(String name) { showLocationDetails(name); } /** {@inheritDoc} */ public void onStatusSelectionChanged(Status status, boolean selected) { if (selected) { m_statusFilter.addStatus(status); } else { m_statusFilter.removeStatus(status); } updateAllMarkerStates(); m_view.updateLocationList(getLocationsForLocationPanel()); } /** {@inheritDoc} */ public void onApplicationSelected(final ApplicationSelectedEvent event) { final String applicationName = event.getApplicationname(); final ApplicationInfo app = m_dataManager.getApplicationInfo(applicationName); //App maybe null if the user types an invalid name if(app == null) { return; } // Add the application to the selected application list m_applicationFilter.addApplication(app); updateAllMarkerStates(); m_view.updateSelectedApplications(m_applicationFilter.getApplications()); m_remoteService.getApplicationDetails(applicationName, new AsyncCallback<ApplicationDetails>() { public void onFailure(final Throwable t) { // TODO: Do something on failure. } public void onSuccess(final ApplicationDetails applicationDetails) { m_eventBus.fireEvent(new ApplicationDetailsRetrievedEvent(applicationDetails)); } }); } /** {@inheritDoc} */ public void onApplicationDeselected(ApplicationDeselectedEvent event) { // Remove the application from the selected application list m_applicationFilter.removeApplication(event.getAppInfo()); updateAllMarkerStates(); m_view.updateSelectedApplications(m_applicationFilter.getApplications()); } /** * <p>locationClicked</p> */ public void locationClicked() { m_selectedFilter = m_locationViewFilter; updateAllMarkerStates(); } /** * <p>applicationClicked</p> */ public void applicationClicked() { m_selectedFilter = m_applicationViewFilter; updateAllMarkerStates(); } /** * <p>getLocationInfoDetails</p> * * @param locationInfo a {@link org.opennms.features.poller.remote.gwt.client.location.LocationInfo} object. * @param locationDetails a {@link org.opennms.features.poller.remote.gwt.client.location.LocationDetails} object. * @return a {@link java.lang.String} object. */ public static String getLocationInfoDetails(final LocationInfo locationInfo, final LocationDetails locationDetails) { final LocationMonitorState state = locationDetails.getLocationMonitorState(); int pollersStarted = state.getMonitorsStarted(); int pollersStopped = state.getMonitorsStopped(); int pollersDisconnected = state.getMonitorsDisconnected(); int services = state.getServices().size(); int servicesWithOutages = state.getServicesDown().size(); int monitorsWithOutages = state.getMonitorsWithServicesDown().size(); StringBuilder sb = new StringBuilder(); sb.append("<div id=\"locationStatus\">"); sb.append("<dl class=\"statusContents\">\n"); sb.append("<dt class=\"").append(state.getStatusDetails().getStatus().getStyle()).append(" statusDt\">").append("Monitors:").append("</dt>\n"); sb.append("<dd class=\"").append(state.getStatusDetails().getStatus().getStyle()).append(" statusDd\">"); sb.append(pollersStarted + " started").append("<br>\n"); sb.append(pollersStopped + " stopped").append("<br>\n"); sb.append(pollersDisconnected + " disconnected").append("\n"); sb.append("</dd>\n"); if (pollersStarted > 0) { // If pollers are started, add on service information String styleName = Status.UP.getStyle(); if (servicesWithOutages > 0) { if (monitorsWithOutages == pollersStarted) { styleName = Status.DOWN.getStyle(); } else { styleName = Status.MARGINAL.getStyle(); } } sb.append("<dt class=\"").append(styleName).append(" statusDt\">").append("Services:").append("</dt>\n"); sb.append("<dd class=\"").append(styleName).append(" statusDd\">"); sb.append(servicesWithOutages).append(" outage").append(servicesWithOutages == 1? "" : "s"); sb.append(" (of ").append(services).append(" service").append(services == 1? "" : "s").append(")").append("<br>\n"); sb.append(monitorsWithOutages).append(" poller").append(monitorsWithOutages == 1? "" : "s").append(" reporting errors").append("\n"); sb.append("</dd>\n"); } sb.append("</div>"); return sb.toString(); } public void updateLocations(Collection<LocationInfo> locations) { LocationUpdater locUpdater = new LocationUpdater(locations); locUpdater.schedule(m_executor); } private RemoteEventService getRemoteEventService() { return m_remoteEventService; } }