/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2013
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.eventbrowser.handlers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.statusreporter.StatusReporter;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.model.application.ui.menu.MHandledItem;
import org.eclipse.e4.ui.services.IServiceConstants;
import alma.acs.nsstatistics.ChannelData;
import alma.acs.nsstatistics.EventModel;
import alma.acs.exceptions.AcsJException;
/**
*
*/
public class SubscribeNCHandler {
@Inject
private UISynchronize uiSync;
/**
* Blocking (popup) status report.
*/
@Inject
private StatusReporter statusReporter;
private EventModel eventModel;
/**
* TODO: Inject UI-specific logger instead of using the one from EventModel
*/
private Logger logger;
/**
* Subscribes / unsubscribes to/from an NC in a non-UI thread.
*/
private abstract class SubscriptionChangeJob extends Job {
protected final ChannelData nc;
SubscriptionChangeJob(ChannelData nc, String jobname) {
super(SubscriptionChangeJob.class.getSimpleName());
this.nc = nc;
}
protected abstract void changeSubscription() throws AcsJException;
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
changeSubscription();
} catch (final Exception ex) {
ex.printStackTrace();
uiSync.syncExec(new Runnable() {
public void run() {
IStatus someStatus = statusReporter.newStatus(IStatus.ERROR, "Subscription to NC(s) failed.", ex);
statusReporter.report(someStatus, StatusReporter.SHOW);
}
});
}
subscriptionChangeJobMap.remove(nc.getName());
return Status.OK_STATUS;
}
}
private class SubscribeJob extends SubscriptionChangeJob {
private SubscribeJob(ChannelData nc) {
super(nc, SubscribeJob.class.getSimpleName());
}
@Override
protected void changeSubscription() throws AcsJException {
eventModel.addChannelSubscription(nc);
logger.info("Subscribed to NC " + nc.getName());
}
}
private class UnsubscribeJob extends SubscriptionChangeJob {
private UnsubscribeJob(ChannelData nc) {
super(nc, UnsubscribeJob.class.getSimpleName());
}
@Override
protected void changeSubscription() throws AcsJException {
eventModel.closeSelectedConsumer(nc);
logger.info("Unsubscribed from NC " + nc.getName());
}
}
/**
* This map contains job subscriptions in progress, or is empty otherwise.
* <p>
* We could just as well loop over multiple subscriptions in a single job,
* because the eclipse job framework seems to run only one job at a time.
*/
private final Map<String, SubscriptionChangeJob> subscriptionChangeJobMap = Collections.synchronizedMap(new HashMap<String, SubscriptionChangeJob>());
@PostConstruct
public void init() {
try {
eventModel = EventModel.getInstance();
} catch (Throwable thr) {
thr.printStackTrace();
IStatus someStatus = statusReporter.newStatus(IStatus.ERROR, "Connection with NCs failed.", thr);
statusReporter.report(someStatus, StatusReporter.SHOW);
throw new RuntimeException(thr);
}
logger = eventModel.getLogger();
}
@CanExecute
public boolean canExecute(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) Object obj, MHandledItem handledItem) {
boolean canExecute = false;
if (subscriptionChangeJobMap.isEmpty()) { // disable while previous subscription change actions are still running
List<ChannelData> ncList = getSelectedNCs(obj);
if (!ncList.isEmpty()) {
canExecute = true;
// if multiple NCs are selected, they must all be in the same subscription state,
// otherwise we don't allow the subscription change
Boolean sharedIsSubscribed = null;
for (ChannelData nc : ncList) {
// all selected NCs must be subscribable
if (!nc.isSubscribable()) {
canExecute = false;
break;
}
if (sharedIsSubscribed == null) {
// the first NC we are checking
sharedIsSubscribed = new Boolean(eventModel.isSubscribed(nc));
}
else {
if (eventModel.isSubscribed(nc) != sharedIsSubscribed.booleanValue()) {
canExecute = false;
break;
}
}
}
// The CHECK context menu item only holds one 'selected' state.
// Since we are dealing with different NCs, the real state is in the domain model (em.isSubscribed)
// We dynamically update the menu item state to display the correct toggle symbol.
if (canExecute) {
handledItem.setSelected(sharedIsSubscribed);
}
}
}
return canExecute;
}
/**
* Allows one or many NCs to be selected, which is why we use 'Object' instead of 'ChannelData' as the selection.
*/
@Execute
public void execute(@Named(IServiceConstants.ACTIVE_SELECTION) Object obj, MHandledItem handledItem) {
List<ChannelData> ncList = getSelectedNCs(obj);
for (ChannelData nc : ncList) {
// We simply toggle the subscription, ignoring the CHECK menu item state.
// In fact we have to manually toggle the menu in the end, since the UI-induced toggling got lost
// when eclipse called 'canExecute' right before this method.
boolean previousIsSubscribed = eventModel.isSubscribed(nc);
SubscriptionChangeJob job = ( previousIsSubscribed ? new UnsubscribeJob(nc) : new SubscribeJob(nc) );
subscriptionChangeJobMap.put(nc.getName(), job);
job.schedule();
handledItem.setSelected(!previousIsSubscribed);
}
}
private List<ChannelData> getSelectedNCs(Object selection) {
List<ChannelData> ret = new ArrayList<ChannelData>();
if (selection instanceof ChannelData) {
ret.add((ChannelData)selection);
}
else if (selection instanceof Object[]) {
for (Object obj : (Object[])selection) {
if (obj instanceof ChannelData) {
ret.add((ChannelData)obj);
}
else {
// One selected node that is not an NC makes the whole selection invalid.
// Dropping it silently would be too confusing.
ret.clear();
break;
}
}
}
return ret;
}
@PreDestroy
public void dispose() {
}
}