/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program 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 version 3. * * 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, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package org.flowerplatform.communication.progress_monitor.remote; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import org.flowerplatform.communication.channel.CommunicationChannel; import org.flowerplatform.communication.channel.ICommunicationChannelLifecycleListener; import org.flowerplatform.communication.progress_monitor.ProgressMonitor; import org.flowerplatform.communication.stateful_service.IStatefulClientLocalState; import org.flowerplatform.communication.stateful_service.IStatefulServiceMXBean; import org.flowerplatform.communication.stateful_service.InvokeStatefulClientMethodClientCommand; import org.flowerplatform.communication.stateful_service.RegularStatefulService; import org.flowerplatform.communication.stateful_service.RemoteInvocation; import org.flowerplatform.communication.stateful_service.StatefulServiceInvocationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Sorin * */ public class ProgressMonitorStatefulService extends RegularStatefulService<CommunicationChannel, HashMap<String, ProgressMonitor>> implements IStatefulServiceMXBean, ICommunicationChannelLifecycleListener { public static final Logger logger = LoggerFactory.getLogger(ProgressMonitorStatefulService.class); /** * */ public static final String SERVICE_ID = "ProgressMonitorStatefulService"; // TODO Sorin : De rezolvat : pe server cand se detecteaza deconectarea se pastreaza progressMonitors pentru operatiile care nu pot fi oprite. // Insa deconectarea pe server se detecteaza abia dupa 30 secunde, pe cand pe client se detecteaza imediat si se poate conecta inapoi. // In acest caz la subscribe in urma reconectarii progressMonitors inca nu se afla in acest map iar pe client o sa se inchida dialogul // desi exista asociat altui canal de comunicare care inca nu stie ca a fost terminat. private HashMap<String, ProgressMonitor> canceledProgressMonitorsFromDisconnect = new HashMap<String, ProgressMonitor>(); /////////////////////////////////////////////////////////////// // JMX Methods /////////////////////////////////////////////////////////////// /** * */ protected void printStatefulDataForClient(StringBuffer stringBuffer, String linePrefix, CommunicationChannel client, HashMap<String, ProgressMonitor> data) { super.printStatefulDataForClient(stringBuffer, linePrefix, client, data); for (ProgressMonitor progressMonitor : data.values()) stringBuffer.append(linePrefix).append(" ").append(progressMonitor).append("\n"); } @Override public Collection<String> getStatefulClientIdsForCommunicationChannel(CommunicationChannel communicationChannel) { HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(communicationChannel); if (channelProgressMonitors == null) return Collections.emptyList(); else return channelProgressMonitors.keySet(); } /////////////////////////////////////////////////////////////// // Normal methods /////////////////////////////////////////////////////////////// /** * */ protected String getStatefulServiceId() { return SERVICE_ID; } /** * */ public ProgressMonitorStatefulService() { clients = new ConcurrentHashMap<CommunicationChannel, HashMap<String, ProgressMonitor>>(); } /** * */ public void dispose() { ProgressMonitor.scheduler.shutdown(); if (!canceledProgressMonitorsFromDisconnect.isEmpty()) logger.warn("The following progress monitors could not be stopped : " + canceledProgressMonitorsFromDisconnect.values()); } /** * */ public void beginProgressMonitor(CommunicationChannel channel, String statefulClientId, String name, int totalWork) { invokeClientMethod(channel, statefulClientId, "beginProgressMonitor", new Object[] {name, totalWork}); } /** * */ public void updateProgressMonitor(CommunicationChannel channel, String statefulClientId, String name, double workUntilNow) { HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(channel); if (channelProgressMonitors != null) // Update needed to be done only while the client is present. invokeClientMethod(channel, statefulClientId, "updateProgressMonitor", new Object[] {name, workUntilNow}); } /** * Makes the client to hide it's dialog and unregister it's ProgressMonitorStatefulClient but wont unsubscribe from * server because it is directly done here. * */ public void closeProgressMonitor(CommunicationChannel channel, String statefulClientId) { HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(channel); if (channelProgressMonitors != null) // Operation terminated before the client left. invokeClientMethod(channel, statefulClientId, "closeProgressMonitor", null); unsubscribe(new StatefulServiceInvocationContext(channel, null, statefulClientId), new ProgressMonitorStatefulLocalClient() /* to differentiate between server disconnect = null */); } /** * The sending must be done instantly because otherwise when operations are done in the same thread with the channel, client updated wont be perceived * until the operation finalizes (due to fact that update wait to travel with http response). */ @Override protected void invokeClientMethod(CommunicationChannel statefulClientCommunicationChannel, String statefulClientId, String methodName, Object[] parameters) { statefulClientCommunicationChannel.sendCommandWithPush(new InvokeStatefulClientMethodClientCommand(statefulClientId, methodName, parameters)); } /** * Forces the client to create a ProgressMonitorStatefulClient. It returns the WebProgressMonitor * associated to that stateful client. */ public ProgressMonitor createProgressMonitor(String title, CommunicationChannel channel) { return subscribeInternal(new StatefulServiceInvocationContext(channel), new ProgressMonitorStatefulLocalClient(title, true), true /* created by server */); } /** * Creates a non-cancelable progress monitor (without cancel button displayed). * @author Cristina */ public ProgressMonitor createNonCancelableProgressMonitor(String title, CommunicationChannel channel) { return subscribeInternal(new StatefulServiceInvocationContext(channel), new ProgressMonitorStatefulLocalClient(title, false), true /* created by server */); } /** * Returns the {@link WebProgressMonitor} at which the client previously subscribed * using the <code>progressMonitorStatefulClientId</code>. */ // public ProgressMonitor getProgressMonitor(String progressMonitorStatefulClientId) { // CommunicationChannel channel = CommunicationChannel.threadLocalInstance.get(); // HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(channel); // return channelProgressMonitors.get(progressMonitorStatefulClientId); // } /////////////////////////////////////////////////////////////// // @RemoteInvocation methods /////////////////////////////////////////////////////////////// @Override @RemoteInvocation public void subscribe(StatefulServiceInvocationContext context, IStatefulClientLocalState statefulClientLocalState) { subscribeInternal(context, (ProgressMonitorStatefulLocalClient) statefulClientLocalState, false /* created by client */); } /** * Supports the following cases: * - fake subscription by server which requests the client to create and register a new ProgressMonitorStatefulClient * - subscription by client for a new progress monitor * - subscription by client after a reconnect which tries to recover the progress monitor if it could not be stopped or orders the client to close it's dialog. */ private ProgressMonitor subscribeInternal(StatefulServiceInvocationContext context, ProgressMonitorStatefulLocalClient statefulClientLocalState, boolean subscribeByServer) { CommunicationChannel channel = (CommunicationChannel) context.getCommunicationChannel(); HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(channel); if (channelProgressMonitors == null) { channelProgressMonitors = new HashMap<String, ProgressMonitor>(); clients.put(channel, channelProgressMonitors); } ProgressMonitor progressMonitor = null; if (statefulClientLocalState.afterReconnect) { progressMonitor = canceledProgressMonitorsFromDisconnect.remove(context.getStatefulClientId()); if (progressMonitor == null) { // Could not recover client progress monitor closeProgressMonitor(context.getCommunicationChannel(), context.getStatefulClientId()); return null; } else { progressMonitor.setChannel(context.getCommunicationChannel()); // Updating channel to the new one if (logger.isTraceEnabled()) logger.trace("Restoring to {} with {}", getStatefulServiceId(), progressMonitor.getStatefulClientId()); } } else { // First subscribe from server or client progressMonitor = new ProgressMonitor(channel, statefulClientLocalState.title, context.getStatefulClientId(), subscribeByServer); if (subscribeByServer) { channel.sendCommandWithPush(new CreateProgressMonitorStatefulClientCommand(statefulClientLocalState.title, progressMonitor.getStatefulClientId(), statefulClientLocalState.allowCancellation)); } } logger.info("Subscribing to {} with client {}", getStatefulServiceId(), progressMonitor.getStatefulClientId()); // New from server or client, or progress monitor could not be stopped. channelProgressMonitors.put(progressMonitor.getStatefulClientId(), progressMonitor); return progressMonitor; } /** * Supports the following cases : * - channel destroyed - which cancels all progress monitors and remembers them as proposed to be canceled; * - operation done - while a channel is present or not (work done by server while client still present or left) * */ @RemoteInvocation public void unsubscribe(StatefulServiceInvocationContext context, IStatefulClientLocalState statefulClientLocalState) { HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(context.getCommunicationChannel()); if (statefulClientLocalState == null) { // channel disconnect if (channelProgressMonitors == null) { // no progress monitors registered yet return; } logger.info("Unsubscribing from {} with {}", getStatefulServiceId(), channelProgressMonitors.keySet()); for (ProgressMonitor progressMonitor : channelProgressMonitors.values()) progressMonitor.setCanceled(true); // Just suggests terminating the operation. clients.remove(context.getCommunicationChannel()); canceledProgressMonitorsFromDisconnect.putAll(channelProgressMonitors); } else { // operation done // verify if operation was while the client is present if (channelProgressMonitors != null && channelProgressMonitors.remove(context.getStatefulClientId()) != null) { // was anything removed from "channel's progress monitors list" ? if (logger.isTraceEnabled()) logger.trace("Unsubscribing from {} with {} before client left the server", getStatefulServiceId(), context.getStatefulClientId()); // or verify that the operation was done after the client left the server } else if (canceledProgressMonitorsFromDisconnect.remove(context.getStatefulClientId()) != null) { // was anything removed from "progress monitors proposed to be canceled" ? if (logger.isTraceEnabled()) logger.warn("Unsubscribing from {} with {} after client left the server", getStatefulServiceId(), context.getStatefulClientId()); } logger.info("Unsubscribing from {} with client {}", getStatefulServiceId(), context.getStatefulClientId()); } } /** * Updates the {@link WebProgressMonitor} so that the operation to know that is should terminate. * Note: the operation may choose to ignore the #canceled flag. * */ @RemoteInvocation public void attemptCancelProgressMonitor(StatefulServiceInvocationContext context) { HashMap<String, ProgressMonitor> channelProgressMonitors = clients.get(context.getCommunicationChannel()); ProgressMonitor progressMonitor = channelProgressMonitors.get(context.getStatefulClientId()); if (progressMonitor != null) // Operation may have already terminated when the client requests to cancel it. progressMonitor.setCanceled(true); } @Override public void communicationChannelCreated(CommunicationChannel webCommunicationChannel) { } }