/******************************************************************************* * Copyright (c) 2013, 2015 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 && status.isOK()) { 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); } } }; 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); } }); } } } }