| /******************************************************************************* |
| * Copyright (c) 2013, 2014 Wind River Systems, Inc. and others. All rights reserved. |
| * This program and the accompanying materials are made available under the terms |
| * of the Eclipse Public License v1.0 which accompanies this distribution, and is |
| * available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.tcf.te.tcf.processes.core.model.runtime.services; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.tcf.protocol.IChannel; |
| import org.eclipse.tcf.protocol.IToken; |
| import org.eclipse.tcf.protocol.Protocol; |
| import org.eclipse.tcf.services.IProcesses; |
| import org.eclipse.tcf.services.ISysMonitor; |
| import org.eclipse.tcf.services.ISysMonitor.SysMonitorContext; |
| import org.eclipse.tcf.te.runtime.callback.AsyncCallbackCollector; |
| import org.eclipse.tcf.te.runtime.callback.Callback; |
| import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback; |
| import org.eclipse.tcf.te.runtime.model.interfaces.IContainerModelNode; |
| import org.eclipse.tcf.te.runtime.model.interfaces.IModelNode; |
| import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx; |
| import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx.QueryState; |
| import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx.QueryType; |
| import org.eclipse.tcf.te.runtime.services.ServiceUtils; |
| import org.eclipse.tcf.te.runtime.utils.StatusHelper; |
| import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate; |
| import org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelChannelService; |
| import org.eclipse.tcf.te.tcf.core.model.services.AbstractModelService; |
| import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode.TYPE; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNodeProperties; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModel; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService; |
| import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelUpdateService; |
| import org.eclipse.tcf.te.tcf.processes.core.nls.Messages; |
| |
| /** |
| * Runtime model refresh service implementation. |
| * <p> |
| * <b>Service implementation assumptions</b> |
| * <ul> |
| * <li>Refresh operations do not update the model or the existing process context model nodes directly. Any |
| * refresh operation is creating a parallel tree of nodes which is merged into the model <i>at the end</i> |
| * of the refresh operation.</li> |
| * <li>Refresh operations do not modify the status of asynchronous refreshable context of the refresh operation |
| * root. The caller of the refresh service is responsible for handling the asynchronous refreshable context |
| * state of the refresh operation root.</li> |
| * <li>Refresh operations requested for the same root while still running are queued and the callbacks are fired |
| * all at once when the first refresh operation completes.</li> |
| * <li>Auto-refresh operations are walking the whole process context model node tree, starting from the model root, |
| * and triggers an refresh of all process context model nodes found where the child list query marker is set to done.</li> |
| * </ul> |
| */ |
| public class RuntimeModelRefreshService extends AbstractModelService<IRuntimeModel> implements IRuntimeModelRefreshService { |
| // For each root context to refresh, remember the callbacks to invoke. |
| private final Map<IModelNode, List<ICallback>> ctx2cb = new HashMap<IModelNode, List<ICallback>>(); |
| // The default processes runtime model refresh service delegate |
| /* default */ final IRuntimeModelRefreshService.IDelegate defaultDelegate = new DefaultDelegate(); |
| |
| /** |
| * Default processes runtime model refresh service delegate implementation. |
| */ |
| /* default */ final class DefaultDelegate implements IRuntimeModelRefreshService.IDelegate { |
| private final String[] managedPropertyNames = new String[] { IProcessContextNodeProperties.PROPERTY_CMD_LINE }; |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService.IDelegate#setNodeType(java.lang.String, org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode) |
| */ |
| @Override |
| public void setNodeType(String parentContextId, IProcessContextNode node) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(node); |
| |
| node.setType(parentContextId == null ? TYPE.Process : TYPE.Thread); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService.IDelegate#postRefreshContext(org.eclipse.tcf.protocol.IChannel, org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) |
| */ |
| @Override |
| public void postRefreshContext(final IChannel channel, final IProcessContextNode node, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(channel); |
| Assert.isNotNull(node); |
| Assert.isNotNull(callback); |
| |
| // The channel must be opened, otherwise the query cannot run |
| if (channel.getState() != IChannel.STATE_OPEN) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.RuntimeModelRefreshService_error_channelClosed); |
| callback.done(RuntimeModelRefreshService.this, status); |
| return; |
| } |
| |
| // Get the required services |
| final ISysMonitor sysMonService = channel.getRemoteService(ISysMonitor.class); |
| |
| // The system monitor service must be available |
| if (sysMonService == null) { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| return; |
| } |
| |
| // The context id must be set |
| String contextId = node.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (contextId == null) { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| return; |
| } |
| |
| // Get the command line of the context |
| sysMonService.getCommandLine(contextId, new ISysMonitor.DoneGetCommandLine() { |
| @Override |
| public void doneGetCommandLine(IToken token, Exception error, String[] cmd_line) { |
| node.setProperty(IProcessContextNodeProperties.PROPERTY_CMD_LINE, error == null ? cmd_line : null); |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| }); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService.IDelegate#getManagedPropertyNames() |
| */ |
| @Override |
| public String[] getManagedPropertyNames() { |
| return managedPropertyNames; |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param model The parent model. Must not be <code>null</code>. |
| */ |
| public RuntimeModelRefreshService(IRuntimeModel model) { |
| super(model); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelRefreshService#refresh(org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) |
| */ |
| @Override |
| public void refresh(final ICallback callback) { |
| refresh(callback, 2); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService#refresh(org.eclipse.tcf.te.runtime.interfaces.callback.ICallback, int) |
| */ |
| @Override |
| public void refresh(ICallback callback, int depth) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Get the parent model |
| final IRuntimeModel model = getModel(); |
| Assert.isNotNull(model); |
| |
| // If the parent model is already disposed, the service will drop out immediately |
| if (model.isDisposed()) { |
| if (callback != null) callback.done(this, Status.OK_STATUS); |
| return; |
| } |
| |
| // A refresh for the passed in node is already running if the model |
| // is associated with a callback list in 'ctx2cb'. |
| final boolean isRefreshAlreadyRunning = ctx2cb.containsKey(model); |
| |
| // Queue the callback to invoke once the refresh is done |
| List<ICallback> callbacks = ctx2cb.get(model); |
| if (callbacks == null) { |
| callbacks = new ArrayList<ICallback>(); |
| ctx2cb.put(model, callbacks); |
| } |
| Assert.isNotNull(callbacks); |
| if (callback != null) callbacks.add(callback); |
| |
| // If a refresh is already running, drop out. The callback is already |
| // queued and will be invoked once the refresh operation is done. |
| if (isRefreshAlreadyRunning) return; |
| |
| // The refresh operation is building up a parallel data tree. Pass in an empty container |
| // to receive the fetched children. |
| final IProcessContextNode container = model.getFactory().newInstance(IProcessContextNode.class); |
| // Mark the container as refresh in progress |
| final IAsyncRefreshableCtx containerRefreshable = (IAsyncRefreshableCtx)container.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(containerRefreshable); |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.IN_PROGRESS); |
| // Initiate the refresh of the level 1 children |
| refreshChildrenLevel1(null, depth, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Mark the container refresh as done |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE); |
| |
| // If the refresh succeeded, update the tree |
| if (status.isOK()) { |
| // Process the new child list and merge it with the model |
| model.getService(IRuntimeModelUpdateService.class).updateChildren(model, container); |
| |
| // Walk the tree on check the children at level 2 to determine if there are |
| // nodes which got expanded by the user and must be refreshed therefore too. |
| final List<IProcessContextNode> children = new ArrayList<IProcessContextNode>(); |
| // Get the first level children from the model |
| List<IProcessContextNode> candidates = model.getChildren(IProcessContextNode.class); |
| for (IProcessContextNode candidate : candidates) { |
| // If the child list got not queried for the candidate, skip it |
| IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)candidate.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(refreshable); |
| if (refreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.DONE) continue; |
| // Get the second level children and find those candidates where |
| // the child list query is marked done. Add those candidates to |
| // the list of children to refresh too. |
| List<IProcessContextNode> candidates2 = candidate.getChildren(IProcessContextNode.class); |
| for (IProcessContextNode candidate2 : candidates2) { |
| // Get the asynchronous refreshable for the candidate |
| refreshable = (IAsyncRefreshableCtx)candidate2.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(refreshable); |
| if (refreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.DONE) continue; |
| // This child needs an additional refresh |
| children.add(candidate2); |
| } |
| } |
| |
| // Run the auto-refresh logic for all children we have found |
| final ICallback callback = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Invoke the callbacks |
| invokeCallbacks(model, RuntimeModelRefreshService.this, status); |
| } |
| }; |
| |
| // Create the callback collector to fire once all refresh operations are completed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(callback, new CallbackInvocationDelegate()); |
| |
| // Get the first level of children and check if they are need to be refreshed |
| if (children.size() > 0) { |
| // Initiate the refresh of the children |
| doAutoRefresh(model, children.toArray(new IProcessContextNode[children.size()]), 0, collector); |
| } |
| |
| // Mark the collector initialization done |
| collector.initDone(); |
| } else { |
| // Invoke the callbacks |
| invokeCallbacks(model, RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelRefreshService#refresh(org.eclipse.tcf.te.runtime.model.interfaces.IModelNode, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) |
| */ |
| @Override |
| public void refresh(final IModelNode node, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(node); |
| |
| // Get the parent model |
| final IRuntimeModel model = getModel(); |
| Assert.isNotNull(model); |
| // If the model is already disposed, drop out immediately |
| if (model.isDisposed() || !(node instanceof IProcessContextNode)) { |
| if (callback != null) callback.done(this, Status.OK_STATUS); |
| return; |
| } |
| |
| // Get the context id |
| final String contextId = node.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (contextId == null) { |
| if (callback != null) callback.done(this, Status.OK_STATUS); |
| return; |
| } |
| |
| // A refresh for the passed in node is already running if the node |
| // is associated with a callback list in 'ctx2cb'. |
| final boolean isRefreshAlreadyRunning = ctx2cb.containsKey(node); |
| |
| // Queue the callback to invoke once the refresh is done |
| List<ICallback> callbacks = ctx2cb.get(node); |
| if (callbacks == null) { |
| callbacks = new ArrayList<ICallback>(); |
| ctx2cb.put(node, callbacks); |
| } |
| Assert.isNotNull(callbacks); |
| // Add the current callback to the list of callbacks |
| if (callback != null) callbacks.add(callback); |
| |
| // If a refresh is already running, drop out. The callback is already |
| // queued and will be invoked once the refresh operation is done. |
| if (isRefreshAlreadyRunning) return; |
| |
| // The refresh operation is building up a parallel data tree. Pass in an empty container |
| // to receive the fetched context properties and context children. |
| final IProcessContextNode container = model.getFactory().newInstance(IProcessContextNode.class); |
| // Mark the container as refresh in progress |
| final IAsyncRefreshableCtx containerRefreshable = (IAsyncRefreshableCtx)container.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(containerRefreshable); |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.IN_PROGRESS); |
| // The context id must be set to the container (asserted by the update service) |
| container.setProperty(IProcessContextNodeProperties.PROPERTY_ID, contextId); |
| // The container has the same type as the original node |
| container.setType(((IProcessContextNode)node).getType()); |
| // Initiate the refresh of context |
| refreshContextLevel1(contextId, 2, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Mark the container refresh as done |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE); |
| |
| // If the refresh succeeded, update the original node |
| if (status.isOK()) { |
| // Process the new context node and merge it with the original context node |
| model.getService(IRuntimeModelUpdateService.class).update(node, container); |
| |
| // Walk the tree on check the children at level 2 to determine if there are |
| // nodes which got expanded by the user and must be refreshed therefore too. |
| final List<IProcessContextNode> children = new ArrayList<IProcessContextNode>(); |
| // Get the first level children from the model |
| List<IProcessContextNode> candidates = ((IProcessContextNode)node).getChildren(IProcessContextNode.class); |
| for (IProcessContextNode candidate : candidates) { |
| // If the child list got not queried for the candidate, skip it |
| IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)candidate.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(refreshable); |
| if (refreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.DONE) continue; |
| // Get the second level children and find those candidates where |
| // the child list query is marked done. Add those candidates to |
| // the list of children to refresh too. |
| List<IProcessContextNode> candidates2 = candidate.getChildren(IProcessContextNode.class); |
| for (IProcessContextNode candidate2 : candidates2) { |
| // Get the asynchronous refreshable for the candidate |
| refreshable = (IAsyncRefreshableCtx)candidate2.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(refreshable); |
| if (refreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.DONE) continue; |
| // This child needs an additional refresh |
| children.add(candidate2); |
| } |
| } |
| |
| // Run the auto-refresh logic for all children we have found |
| final ICallback callback = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Invoke the callbacks |
| invokeCallbacks(node, RuntimeModelRefreshService.this, status); |
| } |
| }; |
| |
| // Create the callback collector to fire once all refresh operations are completed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(callback, new CallbackInvocationDelegate()); |
| |
| // Get the first level of children and check if they are need to be refreshed |
| if (children.size() > 0) { |
| // Initiate the refresh of the children |
| doAutoRefresh(model, children.toArray(new IProcessContextNode[children.size()]), 0, collector); |
| } |
| |
| // Mark the collector initialization done |
| collector.initDone(); |
| } else { |
| // Invoke the callbacks |
| invokeCallbacks(node, RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService#autoRefresh(org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) |
| */ |
| @Override |
| public void autoRefresh(ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Get the parent model |
| final IRuntimeModel model = getModel(); |
| |
| // If the parent model is already disposed, the service will drop out immediately |
| if (model.isDisposed()) { |
| if (callback != null) callback.done(this, Status.OK_STATUS); |
| return; |
| } |
| |
| // Determine if there is already a model refresh running. |
| // A model refresh can be initiated via refresh(...) or autoRefresh(...). |
| final boolean isRefreshAlreadyRunning = ctx2cb.containsKey(model); |
| |
| // Queue the callback to invoke once the refresh is done |
| List<ICallback> callbacks = ctx2cb.get(model); |
| if (callbacks == null) { |
| callbacks = new ArrayList<ICallback>(); |
| ctx2cb.put(model, callbacks); |
| } |
| Assert.isNotNull(callbacks); |
| // Add the current callback to the list of callbacks |
| if (callback != null) callbacks.add(callback); |
| |
| // If a refresh is already running, drop out. The callback is already |
| // queued and will be invoked once the refresh operation is done. |
| if (isRefreshAlreadyRunning) return; |
| |
| // Create the inner callback which will invoke all queued callbacks |
| final ICallback innerCallback = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Invoke the callbacks |
| invokeCallbacks(model, RuntimeModelRefreshService.this, status); |
| } |
| }; |
| |
| // Create the callback collector to fire once all refresh operations are completed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(innerCallback, new CallbackInvocationDelegate()); |
| |
| // Get the first level of children and check if they are need to be refreshed |
| List<IProcessContextNode> children = model.getChildren(IProcessContextNode.class); |
| if (children.size() > 0) { |
| // Initiate the refresh of the children |
| doAutoRefresh(model, children.toArray(new IProcessContextNode[children.size()]), 0, collector); |
| } |
| |
| // Mark the collector initialization done |
| collector.initDone(); |
| } |
| |
| // ----- Non-API refresh methods ----- |
| |
| /** |
| * Performs the auto refresh of the given nodes. |
| * |
| * @param model The runtime model. Must not be <code>null</code>. |
| * @param nodes The nodes. Must not be <code>null</code>. |
| * @param index The index of the node to refresh within the nodes array. Must be greater or equal than 0 and less than the array length. |
| * @param collector The callback collector. Must not be <code>null</code>. |
| */ |
| /* default */ void doAutoRefresh(final IRuntimeModel model, final IProcessContextNode[] nodes, final int index, final AsyncCallbackCollector collector) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(model); |
| Assert.isNotNull(nodes); |
| Assert.isTrue(index >= 0 && index < nodes.length); |
| Assert.isNotNull(collector); |
| |
| final IProcessContextNode node = nodes[index]; |
| |
| // Get the asynchronous refresh context adapter |
| final IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)node.getAdapter(IAsyncRefreshableCtx.class); |
| if (refreshable != null) { |
| // Schedule a refresh if the node got refreshed before |
| if (refreshable.getQueryState(QueryType.CHILD_LIST).equals(QueryState.DONE)) { |
| // Create a new callback for the collector to wait for |
| final ICallback callback = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // We need a reference to the outer callback (== this) |
| final ICallback outerCallback = this; |
| // Create the inner callback |
| final ICallback innerCallback = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // More nodes to process? |
| int newIndex = index + 1; |
| if (newIndex < nodes.length) { |
| doAutoRefresh(model, nodes, newIndex, collector); |
| } |
| // Remove the outer callback from the collector |
| collector.removeCallback(outerCallback); |
| } |
| }; |
| |
| // If the node has children, process them first |
| List<IProcessContextNode> children = node.getChildren(IProcessContextNode.class); |
| if (children.size() > 0) { |
| // Create a new callback collector for processing the children |
| final AsyncCallbackCollector childCollector = new AsyncCallbackCollector(innerCallback, new CallbackInvocationDelegate()); |
| // Initiate the refresh of the children |
| doAutoRefresh(model, children.toArray(new IProcessContextNode[children.size()]), 0, childCollector); |
| // Mark the collector initialization done |
| childCollector.initDone(); |
| } else { |
| // Invoke the inner callback right away |
| innerCallback.done(this, Status.OK_STATUS); |
| } |
| } |
| }; |
| collector.addCallback(callback); |
| |
| // Get the context id |
| final String contextId = node.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (contextId == null) { |
| callback.done(this, Status.OK_STATUS); |
| return; |
| } |
| |
| // The refresh operation is building up a parallel data tree. Pass in an empty container |
| // to receive the fetched context properties and context children. |
| final IProcessContextNode container = model.getFactory().newInstance(IProcessContextNode.class); |
| // Mark the container as refresh in progress |
| final IAsyncRefreshableCtx containerRefreshable = (IAsyncRefreshableCtx)container.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(containerRefreshable); |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.IN_PROGRESS); |
| // The context id must be set to the container (asserted by the update service) |
| container.setProperty(IProcessContextNodeProperties.PROPERTY_ID, contextId); |
| // The container has the same type as the original node |
| container.setType(node.getType()); |
| // Initiate the refresh of context (only node and the direct children) |
| refreshContextLevel1(contextId, 1, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Mark the container refresh as done |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE); |
| |
| // If the refresh succeeded, update the original node |
| if (status.isOK()) { |
| // Auto refresh requires to update the children of any new child found for |
| // the node refreshed. Collect all child nodes not being children of the original node. |
| List<IProcessContextNode> oldChildren = node.getChildren(IProcessContextNode.class); |
| List<IProcessContextNode> newChildren = container.getChildren(IProcessContextNode.class); |
| for (IProcessContextNode child : oldChildren) { |
| // Get the context id of the exiting child |
| String id = child.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (id == null) continue; |
| // Find the context id in the new children list |
| IProcessContextNode node = null; |
| for (IProcessContextNode candidate : newChildren) { |
| if (id.equals(candidate.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID))) { |
| node = candidate; |
| break; |
| } |
| } |
| // If found in the new children list, remove it |
| if (node != null) newChildren.remove(node); |
| } |
| |
| // Process the new context node and merge it with the original context node |
| model.getService(IRuntimeModelUpdateService.class).update(node, container); |
| |
| // If there are any new children detected, update the child list of those new children |
| if (newChildren.size() > 0) { |
| // Create the collector firing the final callback at the end |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(callback, new CallbackInvocationDelegate()); |
| // Get the real children list |
| oldChildren = node.getChildren(IProcessContextNode.class); |
| |
| for (IProcessContextNode child : newChildren) { |
| // Get the context id of the child |
| String id = child.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (id == null) continue; |
| |
| // Find the real child node |
| IProcessContextNode realChild = null; |
| for (IProcessContextNode candidate : oldChildren) { |
| if (id.equals(candidate.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID))) { |
| realChild = candidate; |
| break; |
| } |
| } |
| if (realChild == null) continue; |
| |
| // The refresh operation is building up a parallel data tree. Pass in an empty container |
| // to receive the fetched context properties and context children. |
| final IProcessContextNode container = model.getFactory().newInstance(IProcessContextNode.class); |
| // Mark the container as refresh in progress |
| final IAsyncRefreshableCtx containerRefreshable = (IAsyncRefreshableCtx)container.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(containerRefreshable); |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.IN_PROGRESS); |
| // The context id must be set to the container (asserted by the update service) |
| container.setProperty(IProcessContextNodeProperties.PROPERTY_ID, contextId); |
| // The container has the same type as the original node |
| container.setType(node.getType()); |
| |
| // Create the callback to invoke |
| final IProcessContextNode finRealChild = realChild; |
| final ICallback cb = new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Mark the container refresh as done |
| containerRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE); |
| |
| // If the refresh succeeded, update the original child node |
| if (status.isOK()) { |
| model.getService(IRuntimeModelUpdateService.class).updateChildren(finRealChild, container); |
| } |
| |
| // Remove the callback from the collector |
| if (status.getException() != null) { |
| collector.handleError(status.getException()); |
| } else { |
| collector.removeCallback(this); |
| } |
| } |
| }; |
| collector.addCallback(cb); |
| |
| // Refresh the children of the new child |
| refreshChildrenLevel1(id, 1, container, cb); |
| } |
| |
| collector.initDone(); |
| } else { |
| // Invoke the callback |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } else { |
| // Invoke the callback |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Refresh the context properties for the given context id. |
| * <p> |
| * The method fetches the children of the given context id and generates a new set of process |
| * context nodes to represent the children until the max depth is reached. |
| * <p> |
| * The fetched properties are added to the given container. |
| * |
| * @param contextId The context id. Must not be <code>null</code>. |
| * @param maxDepth The max depth of the tree to refresh. Must be greater than 0. |
| * @param container The container. Must not be <code>null</code>. |
| * @param callback The callback to invoke once the operation is completed. Must not be <code>null</code>. |
| */ |
| protected void refreshContextLevel1(final String contextId, final int maxDepth, final IProcessContextNode container, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(contextId); |
| Assert.isNotNull(container); |
| Assert.isNotNull(callback); |
| |
| // Make sure that the callback is invoked even for unexpected cases |
| try { |
| // Get an open channel |
| IModelChannelService channelService = getModel().getService(IModelChannelService.class); |
| channelService.openChannel(new IModelChannelService.DoneOpenChannel() { |
| @Override |
| public void doneOpenChannel(Throwable error, final IChannel channel) { |
| if (error == null) { |
| // Query the context properties |
| refreshContext(channel, contextId, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| if (status.isOK()) { |
| // Query the first level child contexts |
| refreshChildContexts(channel, contextId, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Refresh the next level if the depth is still larger than 0 |
| if (maxDepth < 0 || maxDepth - 1 > 0) { |
| List<IProcessContextNode> children = container.getChildren(IProcessContextNode.class); |
| Assert.isNotNull(children); |
| refreshChildrenLevelN(channel, children.toArray(new IProcessContextNode[children.size()]), maxDepth - 1, callback); |
| } else { |
| // Refresh completed, invoke the callback |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } else { |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } else { |
| callback.done(RuntimeModelRefreshService.this, new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), error.getLocalizedMessage(), error)); |
| } |
| } |
| }); |
| } catch (Throwable e) { |
| callback.done(RuntimeModelRefreshService.this, new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), e.getLocalizedMessage(), e)); |
| } |
| } |
| |
| /** |
| * Fetches the first level of children for the given parent context id. |
| * <p> |
| * The method fetches the first level of children and generates a new set of process |
| * context nodes to represent the children. Each child is refresh itself until the max |
| * depth is reached. |
| * <p> |
| * The fetched first level children are added to the given container. |
| * |
| * @param parentContextId The parent context id or <code>null</code> for the root context. |
| * @param maxDepth The max depth of the tree to refresh. Must be greater than 0. |
| * @param container The container. Must not be <code>null</code>. |
| * @param callback The callback to invoke once the operation is completed. Must not be <code>null</code>. |
| */ |
| /* default */ void refreshChildrenLevel1(final String parentContextId, final int maxDepth, final IProcessContextNode container, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(container); |
| Assert.isNotNull(callback); |
| |
| // Make sure that the callback is invoked even for unexpected cases |
| try { |
| // Get an open channel |
| IModelChannelService channelService = getModel().getService(IModelChannelService.class); |
| channelService.openChannel(new IModelChannelService.DoneOpenChannel() { |
| @Override |
| public void doneOpenChannel(Throwable error, final IChannel channel) { |
| if (error == null) { |
| // Query the first level child contexts |
| refreshChildContexts(channel, parentContextId, container, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Refresh the next level if the depth is still larger than 0 |
| if (maxDepth < 0 || maxDepth - 1 > 0) { |
| List<IProcessContextNode> children = container.getChildren(IProcessContextNode.class); |
| Assert.isNotNull(children); |
| refreshChildrenLevelN(channel, children.toArray(new IProcessContextNode[children.size()]), maxDepth - 1, callback); |
| } else { |
| // Refresh completed, invoke the callback |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } |
| }); |
| } else { |
| callback.done(RuntimeModelRefreshService.this, new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), error.getLocalizedMessage(), error)); |
| } |
| } |
| }); |
| } catch (Throwable e) { |
| callback.done(RuntimeModelRefreshService.this, new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), e.getLocalizedMessage(), e)); |
| } |
| } |
| |
| /** |
| * Fetches the children of the given parent context nodes. |
| * <p> |
| * The method calls itself recursively until <code>maxDepth - 1 == 0</code> is true. |
| * |
| * @param channel An open channel. Must not be <code>null</code>. |
| * @param parents The parent contexts. Must not be <code>null</code>. |
| * @param maxDepth The max depth of the tree to refresh. Must be greater than 0. |
| * @param callback The callback to invoke once the operation is completed. Must not be <code>null</code>. |
| */ |
| /* default */ void refreshChildrenLevelN(final IChannel channel, final IProcessContextNode[] parents, final int maxDepth, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(channel); |
| Assert.isNotNull(parents); |
| Assert.isNotNull(callback); |
| |
| // The channel must be opened, otherwise the query cannot run |
| if (channel.getState() != IChannel.STATE_OPEN) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.RuntimeModelRefreshService_error_channelClosed); |
| callback.done(RuntimeModelRefreshService.this, status); |
| return; |
| } |
| |
| // If the parents list is empty, there is nothing to refresh |
| if (parents.length == 0) { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| return; |
| } |
| |
| // The callback collector to be fired if the children of all parents got fully refreshed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Refresh the next level if the depth is still larger than 0 |
| if (maxDepth < 0 || maxDepth - 1 > 0) { |
| // The callback collector to be fired if the children of all children of all parent contexts got fully refreshed |
| final AsyncCallbackCollector collector2 = new AsyncCallbackCollector(callback, new CallbackInvocationDelegate()); |
| |
| for (IProcessContextNode parent : parents) { |
| List<IProcessContextNode> children = parent.getChildren(IProcessContextNode.class); |
| Assert.isNotNull(children); |
| refreshChildrenLevelN(channel, children.toArray(new IProcessContextNode[children.size()]), maxDepth - 1, new AsyncCallbackCollector.SimpleCollectorCallback(collector2)); |
| } |
| |
| collector2.initDone(); |
| } else { |
| // Refresh completed, invoke the callback |
| callback.done(RuntimeModelRefreshService.this, status); |
| } |
| } |
| }, new CallbackInvocationDelegate()); |
| |
| // Loop the parent contexts and refresh the children of each one. |
| for (final IProcessContextNode parent : parents) { |
| // Get the context id of the parent. Must be not null here. |
| String parentContextId = parent.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID); |
| if (parentContextId == null) continue; |
| // Create the callback |
| final ICallback cb = new AsyncCallbackCollector.SimpleCollectorCallback(collector); |
| // Get the asynchronous refresh context adapter |
| final IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)parent.getAdapter(IAsyncRefreshableCtx.class); |
| Assert.isNotNull(refreshable); |
| // If not IN_PROGRESS, initiate the refresh |
| if (!refreshable.getQueryState(QueryType.CHILD_LIST).equals(QueryState.IN_PROGRESS)) { |
| // Mark the refresh as in progress |
| refreshable.setQueryState(QueryType.CHILD_LIST, QueryState.IN_PROGRESS); |
| // Don't send change events while refreshing |
| final boolean changed = parent.setChangeEventsEnabled(false); |
| // Refresh the children of the parent context |
| refreshChildContexts(channel, parentContextId, parent, new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Mark the refresh as done |
| refreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE); |
| // Re-enable the change events if they had been enabled before |
| if (changed) parent.setChangeEventsEnabled(true); |
| // Invoke the callback |
| cb.done(RuntimeModelRefreshService.this, status); |
| } |
| }); |
| } else { |
| // Invoke the callback |
| cb.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| } |
| |
| collector.initDone(); |
| } |
| |
| /** |
| * Refresh the properties of the given context id using the given channel. |
| * All context properties are <i>added</i> to the passed in node. |
| * |
| * @param channel An open channel. Must not be <code>null</code>. |
| * @param contextId The context id. Must not be <code>null</code>. |
| * @param node The node. Must not be <code>null</code>. |
| * @param callback The callback to invoke once the operation is completed. Must not be <code>null</code>. |
| */ |
| /* default */ void refreshContext(final IChannel channel, final String contextId, final IProcessContextNode node, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(channel); |
| Assert.isNotNull(contextId); |
| Assert.isNotNull(node); |
| Assert.isNotNull(callback); |
| |
| // The channel must be opened, otherwise the query cannot run |
| if (channel.getState() != IChannel.STATE_OPEN) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.RuntimeModelRefreshService_error_channelClosed); |
| callback.done(RuntimeModelRefreshService.this, status); |
| return; |
| } |
| |
| // Get the required services |
| final IProcesses service = channel.getRemoteService(IProcesses.class); |
| final ISysMonitor sysMonService = channel.getRemoteService(ISysMonitor.class); |
| |
| // At least the processes and the system monitor service must be available |
| if (service == null || sysMonService == null) { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| return; |
| } |
| |
| // Callback collector to fire once the system monitor and process context queries completed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Determine if a delegate is registered |
| IRuntimeModelRefreshService.IDelegate delegate = ServiceUtils.getDelegateServiceDelegate(channel.getRemotePeer(), channel.getRemotePeer(), IRuntimeModelRefreshService.IDelegate.class); |
| // Run the post refresh context delegate |
| if (delegate == null) delegate = defaultDelegate; |
| Assert.isNotNull(delegate); |
| delegate.postRefreshContext(channel, node, callback); |
| } |
| }, new CallbackInvocationDelegate()); |
| |
| // Query the system monitor context object |
| final ICallback cb1 = new AsyncCallbackCollector.SimpleCollectorCallback(collector); |
| sysMonService.getContext(contextId, new ISysMonitor.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, SysMonitorContext context) { |
| // Ignore errors. Some of the context might be OS context we do not have |
| // permissions to read the properties from. |
| node.setSysMonitorContext(context); |
| // Invoke the callback |
| cb1.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| }); |
| |
| // Query the process context object |
| final ICallback cb2 = new AsyncCallbackCollector.SimpleCollectorCallback(collector); |
| service.getContext(contextId, new IProcesses.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, IProcesses.ProcessContext context) { |
| // Errors are ignored |
| node.setProcessContext(context); |
| // Set the context name from the process context if available |
| if (context != null) node.setProperty(IProcessContextNodeProperties.PROPERTY_NAME, context.getName()); |
| // Invoke the callback |
| cb2.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| }); |
| |
| collector.initDone(); |
| } |
| |
| /** |
| * Refresh the child contexts of the given parent context id using the given channel. |
| * All child contexts are <i>added</i> to the passed in container. |
| * |
| * @param channel An open channel. Must not be <code>null</code>. |
| * @param parentContextId The parent context id or <code>null</code> for the root context. |
| * @param container The container. Must not be <code>null</code>. |
| * @param callback The callback to invoke once the operation is completed. Must not be <code>null</code>. |
| */ |
| /* default */ void refreshChildContexts(final IChannel channel, final String parentContextId, final IContainerModelNode container, final ICallback callback) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(channel); |
| Assert.isNotNull(container); |
| Assert.isNotNull(callback); |
| |
| // The channel must be opened, otherwise the query cannot run |
| if (channel.getState() != IChannel.STATE_OPEN) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.RuntimeModelRefreshService_error_channelClosed); |
| callback.done(RuntimeModelRefreshService.this, status); |
| return; |
| } |
| |
| // Get the required services |
| final IProcesses service = channel.getRemoteService(IProcesses.class); |
| final ISysMonitor sysMonService = channel.getRemoteService(ISysMonitor.class); |
| |
| // At least the processes and the system monitor service must be available |
| if (service == null || sysMonService == null) { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| return; |
| } |
| |
| // Get the child context id's of the given parent context id |
| sysMonService.getChildren(parentContextId, new ISysMonitor.DoneGetChildren() { |
| @Override |
| public void doneGetChildren(IToken token, Exception error, String[] context_ids) { |
| if (error == null) { |
| if (context_ids != null && context_ids.length > 0) { |
| // Callback collector to fire the passed in callback once all child contexts got fully refreshed |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(callback, new CallbackInvocationDelegate()); |
| |
| // Loop the returned context id's and query the context data |
| for (String id : context_ids) { |
| final String contextId = id; |
| |
| // Create the context node for the current context id |
| final IProcessContextNode node = createContextNodeFrom(contextId); |
| Assert.isNotNull(node); |
| // Add the node to the container |
| container.add(node); |
| |
| // Callback collector to fire once the system monitor and process context queries completed |
| final ICallback innerCallback = new AsyncCallbackCollector.SimpleCollectorCallback(collector); |
| final AsyncCallbackCollector innerCollector = new AsyncCallbackCollector(new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| // Determine if a delegate is registered |
| IRuntimeModelRefreshService.IDelegate delegate = ServiceUtils.getDelegateServiceDelegate(channel.getRemotePeer(), channel.getRemotePeer(), IRuntimeModelRefreshService.IDelegate.class); |
| // Determine the node type |
| if (delegate != null) delegate.setNodeType(parentContextId, node); |
| // Fallback to the default delegate if node type is not set by delegate |
| if (node.getType() == TYPE.Unknown) defaultDelegate.setNodeType(parentContextId, node); |
| |
| // Run the post refresh context delegate |
| if (delegate == null) delegate = defaultDelegate; |
| Assert.isNotNull(delegate); |
| delegate.postRefreshContext(channel, node, innerCallback); |
| } |
| }, new CallbackInvocationDelegate()); |
| |
| // Query the system monitor context object |
| final ICallback cb1 = new AsyncCallbackCollector.SimpleCollectorCallback(innerCollector); |
| sysMonService.getContext(contextId, new ISysMonitor.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, SysMonitorContext context) { |
| // Ignore errors. Some of the context might be OS context we do not have |
| // permissions to read the properties from. |
| node.setSysMonitorContext(context); |
| // Invoke the callback |
| cb1.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| }); |
| |
| // Query the process context object |
| final ICallback cb2 = new AsyncCallbackCollector.SimpleCollectorCallback(innerCollector); |
| service.getContext(contextId, new IProcesses.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, IProcesses.ProcessContext context) { |
| // Errors are ignored |
| node.setProcessContext(context); |
| // Set the context name from the process context if available |
| if (context != null) node.setProperty(IProcessContextNodeProperties.PROPERTY_NAME, context.getName()); |
| // Invoke the callback |
| cb2.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| }); |
| |
| innerCollector.initDone(); |
| } |
| |
| collector.initDone(); |
| } else { |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } |
| } else { |
| if (StatusHelper.unwrapErrorReport(error.getLocalizedMessage()).equals("Invalid context")) { //$NON-NLS-1$ |
| // OK, the context got invalid during the query. Mark the |
| // context as invalid and return with OK to keep the refresh going |
| container.setProperty(IProcessContextNodeProperties.PROPERTY_INVALID_CTX, true); |
| callback.done(RuntimeModelRefreshService.this, Status.OK_STATUS); |
| } else { |
| callback.done(RuntimeModelRefreshService.this, new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), error.getLocalizedMessage(), error)); |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Create a process context node instance for the given context id. |
| * |
| * @param contextId The context id. Must not be <code>null</code>. |
| * @return The process context node instance. |
| */ |
| /* default */ IProcessContextNode createContextNodeFrom(String contextId) { |
| Assert.isNotNull(contextId); |
| |
| // Create a context node and associate the given context |
| IProcessContextNode node = getModel().getFactory().newInstance(IProcessContextNode.class); |
| |
| // Set the context id |
| node.setProperty(IProcessContextNodeProperties.PROPERTY_ID, contextId); |
| |
| return node; |
| } |
| |
| // ----- Utility methods ------ |
| |
| /** |
| * Invoke all pending callbacks for the given context. |
| * <p> |
| * Each callback is invoked as single runnable dispatched to the |
| * TCF event dispatch thread. |
| * |
| * @param context The context. Must not be <code>null</code>. |
| * @param caller The caller of the callback or <code>null</code>. |
| * @param status The status. Must not be <code>null</code>. |
| */ |
| protected void invokeCallbacks(final IModelNode context, final Object caller, final IStatus status) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(context); |
| Assert.isNotNull(status); |
| |
| List<ICallback> callbacks = ctx2cb.remove(context); |
| if (callbacks != null) { |
| for (final ICallback callback : callbacks) { |
| Protocol.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| callback.done(caller, status); |
| } |
| }); |
| } |
| } |
| } |
| } |