/******************************************************************************* * Copyright (c) 2011, 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.locator.nodes; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.te.core.interfaces.IConnectable; import org.eclipse.tcf.te.core.utils.ConnectStateHelper; import org.eclipse.tcf.te.runtime.callback.Callback; import org.eclipse.tcf.te.runtime.concurrent.util.ExecutorsUtil; import org.eclipse.tcf.te.runtime.events.EventManager; import org.eclipse.tcf.te.runtime.events.NotifyEvent; import org.eclipse.tcf.te.runtime.interfaces.IConditionTester; 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.ContainerModelNode; import org.eclipse.tcf.te.runtime.properties.PropertiesContainer; import org.eclipse.tcf.te.runtime.services.ServiceManager; import org.eclipse.tcf.te.runtime.services.interfaces.IDelegateService; import org.eclipse.tcf.te.runtime.services.interfaces.IService; import org.eclipse.tcf.te.runtime.services.interfaces.ISimulatorService; import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepperOperationService; import org.eclipse.tcf.te.runtime.stepper.utils.StepperHelper; import org.eclipse.tcf.te.runtime.utils.StatusHelper; import org.eclipse.tcf.te.tcf.core.interfaces.IPeerProperties; import org.eclipse.tcf.te.tcf.locator.activator.CoreBundleActivator; import org.eclipse.tcf.te.tcf.locator.interfaces.IStepperServiceOperations; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerModel; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNodeProperties; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNodeProvider; import org.eclipse.tcf.te.tcf.locator.interfaces.services.IPeerModelUpdateService; import org.eclipse.tcf.te.tcf.locator.nls.Messages; import org.eclipse.tcf.te.tcf.locator.utils.SimulatorUtils; /** * Default peer model implementation. */ public class PeerNode extends ContainerModelNode implements IPeerNode, IPeerNodeProvider { // Reference to the parent locator model private final IPeerModel model; // Reference to the peer id (cached for performance optimization) private String peerId; private boolean isValid = true; /** * Constructor. * * @param model The parent locator model. Must not be <code>null</code>. * @param peer The peer. Must not be <code>null</code>. */ public PeerNode(IPeerModel model, IPeer peer) { super(); Assert.isNotNull(model); this.model = model; Assert.isNotNull(peer); // Set the default properties before enabling the change events. // The properties changed listeners should not be called from the // constructor. setProperty(IPeerNodeProperties.PROPERTY_INSTANCE, peer); setProperty(IPeerNodeProperties.PROPERTY_CONNECT_STATE, IConnectable.STATE_DISCONNECTED); // Initialize the peer id peerId = peer.getID(); Assert.isNotNull(peerId); // Peer model nodes can change the node parent at any time allowSetParentOnNonNullParent = true; // Peer model nodes does not have a parent by default // -> allow change events with null parent suppressEventsOnNullParent = false; // Enable change events setChangeEventsEnabled(true); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNodeProvider#getPeerModel() */ @Override public IPeerNode getPeerNode() { return this; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#checkThreadAccess() */ @Override protected final boolean checkThreadAccess() { return Protocol.isDispatchThread(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode#getModel() */ @Override public IPeerModel getModel() { return (IPeerModel)getAdapter(IPeerModel.class); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode#getPeer() */ @Override public IPeer getPeer() { return (IPeer)getAdapter(IPeer.class); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode#getPeerId() */ @Override public String getPeerId() { return peerId; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.model.ModelNode#getName() */ @Override public String getName() { return getPeer().getName(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode#isValid() */ @Override public boolean isValid() { final AtomicBoolean valid = new AtomicBoolean(true); IService[] services = ServiceManager.getInstance().getServices(this, IDelegateService.class, false); for (IService service : services) { if (service instanceof IDelegateService) { IPeerNode.IDelegate delegate = ((IDelegateService)service).getDelegate(this, IPeerNode.IDelegate.class); if (delegate != null) { if (delegate.isVisible(this) && !delegate.isValid(this)) { valid.set(false); break; } } } } if (isValid != valid.get()) { isValid = valid.get(); fireChangeEvent(IPeerNodeProperties.PROPERTY_IS_VALID, new Boolean(isValid), new Boolean(valid.get())); } return isValid; } /* (non-Javadoc) * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class) */ @Override public Object getAdapter(final Class adapter) { // NOTE: The getAdapter(...) method can be invoked from many place and // many threads where we cannot control the calls. Therefore, this // method is allowed be called from any thread. final AtomicReference<Object> object = new AtomicReference<Object>(); Runnable runnable = new Runnable() { @Override public void run() { object.set(doGetAdapter(adapter)); } }; if (Protocol.isDispatchThread()) { runnable.run(); } else { Protocol.invokeAndWait(runnable); } return object.get() != null ? object.get() : super.getAdapter(adapter); } /** * Returns an object which is an instance of the given class associated with this object. * Returns <code>null</code> if no such object can be found. * <p> * This method must be called within the TCF dispatch thread! * * @param adapter The adapter class to look up. * @return The adapter or <code>null</code>. */ protected Object doGetAdapter(Class<?> adapter) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ if (IPeerModel.class.isAssignableFrom(adapter)) { return model; } Object peer = getProperty(IPeerNodeProperties.PROPERTY_INSTANCE); // Check with adapter.isAssignableFrom(...) to return the peer instance // correctly if adapter is IPeer.class. if (peer != null && adapter.isAssignableFrom(peer.getClass())) { return peer; } return null; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#toString() */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(getClass().getSimpleName()); Runnable runnable = new Runnable() { @Override public void run() { IPeer peer = getPeer(); buffer.append(": id=" + peer.getID()); //$NON-NLS-1$ buffer.append(", name=" + peer.getName()); //$NON-NLS-1$ } }; if (Protocol.isDispatchThread()) { runnable.run(); } else { Protocol.invokeAndWait(runnable); } buffer.append(", " + super.toString()); //$NON-NLS-1$ return buffer.toString(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof PeerNode) { return getPeerId().equals(((PeerNode)obj).getPeerId()); } return super.equals(obj); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#hashCode() */ @Override public int hashCode() { return getPeerId().hashCode(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#postSetProperties(java.util.Map) */ @Override protected void postSetProperties(Map<String, ?> properties) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(properties); Assert.isNotNull(getPeer()); // New properties applied. Update the element id peerId = getPeer().getID(); Assert.isNotNull(peerId); super.postSetProperties(properties); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.properties.PropertiesContainer#postSetProperty(java.lang.String, java.lang.Object, java.lang.Object) */ @Override public void postSetProperty(String key, Object value, Object oldValue) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(key); Assert.isNotNull(getPeer()); // If the peer instance changed, update the element id if (IPeerNodeProperties.PROPERTY_INSTANCE.equals(key)) { peerId = getPeer().getID(); Assert.isNotNull(peerId); } super.postSetProperty(key, value, oldValue); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.model.ModelNode#isVisible() */ @Override public boolean isVisible() { IPeer peer = getPeer(); boolean visible = peer != null && peer.getAttributes().containsKey(IPeerProperties.PROP_VISIBLE) ? Boolean.valueOf(peer.getAttributes().get(IPeerProperties.PROP_VISIBLE)).booleanValue() : true; if (visible) { IService[] services = ServiceManager.getInstance().getServices(this, IDelegateService.class, false); if (services != null && services.length > 0) { for (IService service : services) { if (service instanceof IDelegateService) { IPeerNode.IDelegate delegate = ((IDelegateService)service).getDelegate(this, IPeerNode.IDelegate.class); if (delegate != null) { return delegate.isVisible(this); } } } } } return visible; } /* (non-Javadoc) * @see org.eclipse.tcf.te.core.interfaces.IConnectable#getConnectState() */ @Override public int getConnectState() { final AtomicInteger state = new AtomicInteger(STATE_UNKNOWN); Protocol.invokeAndWait(new Runnable() { @Override public void run() { if (getProperty(IPeerNodeProperties.PROPERTY_CONNECT_STATE) != null) { state.set(getIntProperty(IPeerNodeProperties.PROPERTY_CONNECT_STATE)); } } }); return state.get(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode#getPeerType() */ @Override public String getPeerType() { if (getPeer() != null) { return getPeer().getAttributes().get(IPeerProperties.PROP_TYPE); } return null; } @Override public boolean setConnectState(final int newState) { final AtomicBoolean result = new AtomicBoolean(false); if (isConnectStateChangeAllowed(newState)) { Protocol.invokeAndWait(new Runnable() { @Override public void run() { result.set(setProperty(IPeerNodeProperties.PROPERTY_CONNECT_STATE, newState)); if (newState != IConnectable.STATE_CONNECTED) { setProperty(IPeerNodeProperties.PROPERTY_WARNINGS, null); } } }); } return result.get(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.core.interfaces.IConnectable#changeConnectState(int, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) */ @Override public void changeConnectState(final int action, ICallback callback, IProgressMonitor monitor) throws IllegalArgumentException { final int oldState = getConnectState(); if (!isConnectStateChangeActionAllowed(action)) { IllegalArgumentException e = new IllegalArgumentException("Cannot change state from '" + ConnectStateHelper.getConnectState(oldState) + "' using action '" + ConnectStateHelper.getConnectState(action) + "'."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if (callback != null) { callback.done(this, StatusHelper.getStatus(e)); } else { throw e; } } String operation = null; int intermediateState = 0; switch (action) { case ACTION_CONNECT: operation = IStepperServiceOperations.CONNECT; intermediateState = STATE_CONNECT_SCHEDULED; break; case ACTION_DISCONNECT: operation = IStepperServiceOperations.DISCONNECT; intermediateState = STATE_DISCONNECT_SCHEDULED; break; case STATE_CONNECTION_LOST: operation = IStepperServiceOperations.CONNECTION_LOST; intermediateState = STATE_CONNECTION_LOST; break; case STATE_CONNECTION_RECOVERING: operation = IStepperServiceOperations.CONNECTION_RECOVERING; intermediateState = STATE_CONNECTION_RECOVERING; break; } IStepperOperationService service = StepperHelper.getService(this, operation); if (service != null) { setConnectState(intermediateState); StepperHelper.scheduleStepperJob(this, operation, service, new PropertiesContainer(), callback, monitor); } else if (callback != null) { callback.done(this, StatusHelper.getStatus(new NullPointerException("Missing stepper operation service for " + getName() + "."))); //$NON-NLS-1$ //$NON-NLS-2$ } } /* (non-Javadoc) * @see org.eclipse.tcf.te.core.interfaces.IConnectable#isConnectStateChangeActionAllowed(int) */ @Override public boolean isConnectStateChangeActionAllowed(int action) { int state = getConnectState(); switch (state) { case STATE_CONNECTED: return isAllowedStateOrAction(action, ACTION_DISCONNECT, STATE_CONNECTION_LOST); case STATE_CONNECTION_RECOVERING: case STATE_CONNECT_SCHEDULED: case STATE_CONNECTING: return isAllowedStateOrAction(action, ACTION_DISCONNECT); case STATE_DISCONNECTED: return isValid() && isAllowedStateOrAction(action, ACTION_CONNECT); case STATE_CONNECTION_LOST: return isAllowedStateOrAction(action, STATE_CONNECTION_RECOVERING); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.core.interfaces.IConnectable#isConnectStateChangeAllowed(int) */ @Override public boolean isConnectStateChangeAllowed(int newState) { int state = getConnectState(); switch (state) { case STATE_CONNECTION_LOST: return isAllowedStateOrAction(newState, STATE_DISCONNECTED, STATE_CONNECTION_RECOVERING); case STATE_CONNECTION_RECOVERING: return isAllowedStateOrAction(newState, STATE_CONNECTED, STATE_DISCONNECT_SCHEDULED, STATE_DISCONNECTING, STATE_DISCONNECTED); case STATE_CONNECTED: return isAllowedStateOrAction(newState, STATE_CONNECTION_LOST, STATE_DISCONNECTED, STATE_DISCONNECT_SCHEDULED, STATE_DISCONNECTING); case STATE_CONNECT_SCHEDULED: return isAllowedStateOrAction(newState, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTED, STATE_DISCONNECT_SCHEDULED, STATE_DISCONNECTING); case STATE_CONNECTING: return isAllowedStateOrAction(newState, STATE_CONNECTED, STATE_DISCONNECT_SCHEDULED, STATE_DISCONNECTING, STATE_DISCONNECTED); case STATE_DISCONNECTED: return isAllowedStateOrAction(newState, STATE_CONNECTED, STATE_CONNECT_SCHEDULED, STATE_CONNECTING); case STATE_DISCONNECT_SCHEDULED: return isAllowedStateOrAction(newState, STATE_DISCONNECTING, STATE_DISCONNECTED); case STATE_DISCONNECTING: return isAllowedStateOrAction(newState, STATE_DISCONNECTED); case STATE_UNKNOWN: return isAllowedStateOrAction(newState, STATE_DISCONNECTED); } return false; } private boolean isAllowedStateOrAction(int stateOrAction, int... allowedStatesOrActions) { for (int allowedStateOrAction : allowedStatesOrActions) { if (stateOrAction == allowedStateOrAction) { return true; } } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened() */ @Override public void onChannelOpened() { } /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable) */ @Override public void onChannelClosed(Throwable error) { final AtomicBoolean connectionLost = new AtomicBoolean(true); if (SimulatorUtils.getSimulatorService(this) != null) { ExecutorsUtil.waitAndExecute(1000, new IConditionTester() { @Override public boolean isConditionFulfilled() { Protocol.invokeAndWait(new Runnable() { @Override public void run() { Object simProcess = getProperty(ISimulatorService.PROP_SIM_INSTANCE); if (simProcess instanceof Process) { try { ((Process)simProcess).exitValue(); connectionLost.set(false); } catch (Exception e) { } } else { connectionLost.set(false); } } }); return !connectionLost.get(); } @Override public void cleanup() { } }); } if (connectionLost.get() && isConnectStateChangeActionAllowed(IConnectable.STATE_CONNECTION_LOST)) { Platform.getLog(CoreBundleActivator.getDefault().getBundle()).log(new Status(IStatus.INFO, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.PeerNode_info_connectionLost, getName()))); changeConnectState(IConnectable.STATE_CONNECTION_LOST, new Callback() { @Override protected void internalDone(Object caller, IStatus status) { Protocol.invokeLater(new Runnable() { @Override public void run() { IPeerModelUpdateService service = getModel().getService(IPeerModelUpdateService.class); service.updatePeerServices(PeerNode.this, null, null); } }); fireNotification(getConnectState()); if (status.isOK() && isConnectStateChangeAllowed(IConnectable.STATE_CONNECTION_RECOVERING)) { changeConnectState(IConnectable.STATE_CONNECTION_RECOVERING, new Callback() { @Override protected void internalDone(Object caller, IStatus status) { if (status.isOK()) { fireNotification(IConnectable.STATE_CONNECTION_RECOVERING); } } }, null); } else { } } }, null); } else if (isConnectStateChangeActionAllowed(IConnectable.ACTION_DISCONNECT)) { Platform.getLog(CoreBundleActivator.getDefault().getBundle()).log(new Status(IStatus.INFO, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.PeerNode_info_connectionDisconnected, getName()))); changeConnectState(IConnectable.ACTION_DISCONNECT, new Callback() { @Override protected void internalDone(Object caller, IStatus status) { Protocol.invokeLater(new Runnable() { @Override public void run() { IPeerModelUpdateService service = getModel().getService(IPeerModelUpdateService.class); service.updatePeerServices(PeerNode.this, null, null); fireNotification(IConnectable.ACTION_DISCONNECT); } }); } }, null); } else { Protocol.invokeLater(new Runnable() { @Override public void run() { IPeerModelUpdateService service = getModel().getService(IPeerModelUpdateService.class); service.updatePeerServices(PeerNode.this, null, null); } }); } } /** * Fire the module notification. * * @param node The module context node. Must not be <code>null</code>. * @param operation The module operation (added, removed, changed). Must not be <code>null</code>. * @param status The status of the operation (success, failed, ...). Must not be <code>null</code> */ protected void fireNotification(int state) { // Show a notification to the user String message = null; switch (state) { case IConnectable.STATE_CONNECTION_LOST: message = Messages.PeerNode_notification_message_connectionLost; break; case IConnectable.STATE_CONNECTION_RECOVERING: message = Messages.PeerNode_notification_message_connectionRecovered; break; case IConnectable.STATE_DISCONNECTED: message = Messages.PeerNode_notification_message_disconnected; break; } if (message != null) { IPropertiesContainer properties = new PropertiesContainer(); properties.setProperty(NotifyEvent.PROP_TITLE_TEXT, getName()); properties.setProperty(NotifyEvent.PROP_TITLE_IMAGE_ID, getPeerType()); properties.setProperty(NotifyEvent.PROP_DESCRIPTION_TEXT, message); NotifyEvent event = new NotifyEvent(getModel(), null, properties); EventManager.getInstance().fireEvent(event); } } /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int) */ @Override public void congestionLevel(int level) { } }