/*******************************************************************************
* Copyright (c) 2012, 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.steps;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
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.IProcesses.ProcessContext;
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.interfaces.properties.IPropertiesContainer;
import org.eclipse.tcf.te.runtime.model.interfaces.IModelNode;
import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
import org.eclipse.tcf.te.runtime.services.ServiceManager;
import org.eclipse.tcf.te.runtime.services.interfaces.IDebugService;
import org.eclipse.tcf.te.runtime.statushandler.StatusHandlerManager;
import org.eclipse.tcf.te.runtime.statushandler.interfaces.IStatusHandler;
import org.eclipse.tcf.te.runtime.statushandler.interfaces.IStatusHandlerConstants;
import org.eclipse.tcf.te.tcf.core.Tcf;
import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate;
import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
import org.eclipse.tcf.te.tcf.core.model.interfaces.IModel;
import org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelRefreshService;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode;
import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator;
import org.eclipse.tcf.te.tcf.processes.core.interfaces.IContextHelpIds;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode;
import org.eclipse.tcf.te.tcf.processes.core.nls.Messages;
/**
* Process attach step implementation.
*/
public class AttachStep {
/**
* Attach to the given list of process context nodes of the given peer model node.
* <p>
* <b>Note:</b> This method must be called from within the TCF dispatch thread.
*
* @param peerNode The peer model. Must not be <code>null</code>.
* @param nodes The list of process context nodes. Must not be <code>null</code>.
* @param callback The callback to invoke once the operation completed, or<code>null</code>.
*/
public void executeAttach(final IPeerNode peerNode, final IProcessContextNode[] nodes, final ICallback callback) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(peerNode);
Assert.isNotNull(nodes);
// Determine if we have to execute the attach at all
final List<IProcessContextNode> nodesToAttach = new ArrayList<IProcessContextNode>();
for (IProcessContextNode node : nodes) {
IPeerNode parentPeerModel = (IPeerNode)node.getAdapter(IPeerNode.class);
if (!peerNode.equals(parentPeerModel)) continue;
// If not yet attached, we have to attach to it
if (node.getProcessContext() != null && !node.getProcessContext().isAttached()) {
if (!nodesToAttach.contains(node)) nodesToAttach.add(node);
}
}
// Anything to attach?
if (!nodesToAttach.isEmpty()) {
// Determine the debug service to attach to the peer node
IDebugService dbgService = ServiceManager.getInstance().getService(peerNode, IDebugService.class, false);
if (dbgService != null) {
// Attach to the peer node first
dbgService.attach(peerNode, new PropertiesContainer(), null, new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
callback.setProperty("launch", getProperty("launch")); //$NON-NLS-1$ //$NON-NLS-2$
Runnable runnable = new Runnable() {
@Override
public void run() {
doAttach(peerNode, Collections.unmodifiableList(nodesToAttach), callback);
}
};
if (Protocol.isDispatchThread()) runnable.run();
else Protocol.invokeLater(runnable);
}
});
} else {
doAttach(peerNode, Collections.unmodifiableList(nodesToAttach), callback);
}
} else {
onDone(callback);
}
}
/**
* Opens a channel and perform the attach to the given process context nodes.
* <p>
* <b>Note:</b> This method must be called from within the TCF dispatch thread.
*
* @param peerNode The peer model. Must not be <code>null</code>.
* @param nodes The process context node. Must not be <code>null</code>.
* @param callback The callback to invoke once the operation completed, or<code>null</code>.
*/
protected void doAttach(final IPeerNode peerNode, final List<IProcessContextNode> nodes, final ICallback callback) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(peerNode);
Assert.isNotNull(nodes);
// Loop the nodes and attach to them
if (!nodes.isEmpty()) {
// Open a channel
Tcf.getChannelManager().openChannel(peerNode.getPeer(), null, new IChannelManager.DoneOpenChannel() {
@Override
public void doneOpenChannel(final Throwable error, final IChannel channel) {
if (error == null) {
final IProcesses service = channel.getRemoteService(IProcesses.class);
if (service != null) {
// Create the callback collector
AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
if (status.getSeverity() == IStatus.ERROR) {
onError(peerNode, status.getMessage(), status.getException(), callback);
} else {
onDone(callback);
}
}
}, new CallbackInvocationDelegate());
for (final IProcessContextNode node: nodes) {
final ICallback callback2 = new AsyncCallbackCollector.SimpleCollectorCallback(collector);
service.getContext(node.getStringProperty(IModelNode.PROPERTY_ID), new IProcesses.DoneGetContext() {
@Override
public void doneGetContext(IToken token, Exception error, ProcessContext context) {
if (error == null && context != null) {
context.attach(new IProcesses.DoneCommand() {
@Override
public void doneCommand(IToken token, Exception error) {
if (error == null) {
// We are attached now, trigger a refresh of the node
IModel model = node.getParent(IModel.class);
Assert.isNotNull(model);
model.getService(IModelRefreshService.class).refresh(node, new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
callback2.done(AttachStep.this, Status.OK_STATUS);
}
});
} else {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
NLS.bind(Messages.AttachStep_error_attach, node.getName()), error);
callback2.done(AttachStep.this, status);
}
}
});
} else {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
NLS.bind(Messages.AttachStep_error_getContext, node.getName()), error);
callback2.done(AttachStep.this, status);
}
}
});
}
// Mark the collector initialization done
collector.initDone();
} else {
onError(peerNode, NLS.bind(Messages.AttachStep_error_missingService, peerNode.getName()), null, callback);
}
} else {
onError(peerNode, NLS.bind(Messages.AttachStep_error_openChannel, peerNode.getName()), error, callback);
}
}
});
} else {
onDone(callback);
}
}
/**
* Error handler. Called if a step failed.
*
* @param channel The channel or <code>null</code>.
* @param context The status handler context. Must not be <code>null</code>:
* @param message The message or <code>null</code>.
* @param error The error or <code>null</code>.
* @param callback The callback or <code>null</code>.
*/
protected void onError(Object context, String message, Throwable error, ICallback callback) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
String detailMessage = error != null ? error.getMessage() : null;
if (detailMessage != null && detailMessage.contains("\n")) { //$NON-NLS-1$
detailMessage = detailMessage.replaceAll("\n", ", "); //$NON-NLS-1$ //$NON-NLS-2$
detailMessage = detailMessage.replaceAll(":, ", ": "); //$NON-NLS-1$ //$NON-NLS-2$
}
String fullMessage = message;
if (fullMessage != null && detailMessage != null) {
fullMessage += NLS.bind(Messages.AttachStep_error_possibleCause, detailMessage);
}
else if (fullMessage == null) {
fullMessage = detailMessage;
}
if (fullMessage != null) {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), fullMessage, error);
if (callback == null) {
IStatusHandler[] handlers = StatusHandlerManager.getInstance().getHandler(context);
if (handlers.length > 0) {
IPropertiesContainer data = new PropertiesContainer();
data.setProperty(IStatusHandlerConstants.PROPERTY_TITLE, Messages.AttachStep_error_title);
data.setProperty(IStatusHandlerConstants.PROPERTY_CONTEXT_HELP_ID, IContextHelpIds.MESSAGE_ATTACH_FAILED);
data.setProperty(IStatusHandlerConstants.PROPERTY_CALLER, this);
handlers[0].handleStatus(status, data, null);
} else {
CoreBundleActivator.getDefault().getLog().log(status);
}
}
else {
callback.done(this, status);
}
}
}
/**
* Done handler. Called if all necessary steps are completed.
*
* @param callback The callback to invoke or <code>null</code>
*/
protected void onDone(ICallback callback) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
if (callback != null) callback.done(this, Status.OK_STATUS);
}
}