Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: fb83c2ebeac9137df88887ba3c8747ad3f2d3c76 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/*******************************************************************************
 * Copyright (c) 2012 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.ui.handler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.te.runtime.callback.Callback;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.persistence.interfaces.IURIPersistenceService;
import org.eclipse.tcf.te.runtime.services.ServiceManager;
import org.eclipse.tcf.te.runtime.statushandler.StatusHandlerUtil;
import org.eclipse.tcf.te.runtime.utils.StatusHelper;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerModel;
import org.eclipse.tcf.te.tcf.locator.interfaces.services.ILocatorModelRefreshService;
import org.eclipse.tcf.te.tcf.locator.model.Model;
import org.eclipse.tcf.te.tcf.ui.help.IContextHelpIds;
import org.eclipse.tcf.te.tcf.ui.nls.Messages;
import org.eclipse.tcf.te.ui.views.Managers;
import org.eclipse.tcf.te.ui.views.ViewsUtil;
import org.eclipse.tcf.te.ui.views.interfaces.ICategory;
import org.eclipse.tcf.te.ui.views.interfaces.IUIConstants;
import org.eclipse.tcf.te.ui.views.interfaces.categories.ICategorizable;
import org.eclipse.tcf.te.ui.views.interfaces.categories.ICategoryManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;

/**
 * Delete handler implementation.
 */
public class DeleteHandler extends AbstractHandler {
	// Remember the shell from the execution event
	private Shell shell = null;

	/* (non-Javadoc)
	 * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
	 */
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		// Get the shell
		shell = HandlerUtil.getActiveShell(event);
		// Get the current selection
		ISelection selection = HandlerUtil.getCurrentSelection(event);
		// Delete the selection
		if (selection != null) {
			delete(selection, new Callback() {
				@Override
				protected void internalDone(Object caller, IStatus status) {
					// Refresh the view
					ViewsUtil.refresh(IUIConstants.ID_EXPLORER);
				}
			});
		}
		// Reset the shell
		shell = null;

		return null;
	}

	/**
	 * Tests if this delete handler can delete the elements of the given
	 * selection.
	 *
	 * @param selection The selection. Must not be <code>null</code>.
	 * @return <code>True</code> if the selection can be deleted by this handler, <code>false</code> otherwise.
	 */
	public boolean canDelete(ISelection selection) {
		Assert.isNotNull(selection);

		boolean canDelete = false;

		// The selection must be a structured selection and must not be empty
		if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
			// Assume the selection to be deletable
			canDelete = true;
			// Iterate the selection. All elements must be of type IPeerModel
			Iterator<?> iterator = ((IStructuredSelection)selection).iterator();
			while (iterator.hasNext()) {
				Object element = iterator.next();
				if (!(element instanceof IPeerModel)) {
					canDelete = false;
					break;
				}

				// Determine if the selected peer model is static
				boolean isStatic = isStatic((IPeerModel)element);
				// Determine if the selected peer model represents an agent
				// started by the current user
				boolean isStartedByCurrentUser = isStartedByCurrentUser((IPeerModel)element);
				// Static nodes can be handled the one way or the other.
				// For dynamic nodes, "delete" means "remove from <category>",
				// and this works only if the parent category is not "Neighborhood".
				if (!isStatic) {
					// Determine the parent categories for the selected node
					ICategory[] categories = getParentCategories(selection, (IPeerModel)element);
					for (ICategory category : categories) {
						if (IUIConstants.ID_CAT_NEIGHBORHOOD.equals(category.getId())) {
							canDelete = false;
							break;
						}
						else if (IUIConstants.ID_CAT_MY_TARGETS.equals(category.getId()) && isStartedByCurrentUser) {
							canDelete = false;
							break;
						}
					}
				}

				if (!canDelete) {
					break;
				}
			}
		}

		return canDelete;
	}

	/**
	 * Determines if the given peer model node is a static node.
	 *
	 * @param node The peer model node. Must not be <code>null</code>.
	 * @return <code>True</code> if the node is static, <code>false</code> otherwise.
	 */
	private boolean isStatic(final IPeerModel node) {
		Assert.isNotNull(node);

		final AtomicBoolean isStatic = new AtomicBoolean();

		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				String value = node.getPeer().getAttributes().get("static.transient"); //$NON-NLS-1$
				isStatic.set(value != null && Boolean.parseBoolean(value.trim()));
			}
		};

		if (Protocol.isDispatchThread()) {
			runnable.run();
		}
		else {
			Protocol.invokeAndWait(runnable);
		}

		return isStatic.get();
	}

	/**
	 * Determines if the given peer model node represents an agent started
	 * by the current user.
	 *
	 * @param node The peer model node. Must not be <code>null</code>.
	 * @return <code>True</code> if the node represents and agent started by the current user,
	 *         <code>false</code> otherwise.
	 */
	private boolean isStartedByCurrentUser(final IPeerModel node) {
		Assert.isNotNull(node);

		final AtomicReference<String> username = new AtomicReference<String>();

		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				username.set(node.getPeer().getUserName());
			}
		};

		if (Protocol.isDispatchThread()) {
			runnable.run();
		}
		else {
			Protocol.invokeAndWait(runnable);
		}

		return System.getProperty("user.name").equals(username.get()); //$NON-NLS-1$
	}

	/**
	 * Returns the parent categories of the selected node based on the
	 * given selection.
	 *
	 * @param selection The selection. Must not be <code>null</code>.
	 * @param node The peer model node. Must not be <code>null</code>.
	 *
	 * @return The list of parent categories of the selected node.
	 */
	private ICategory[] getParentCategories(ISelection selection, IPeerModel node) {
		Assert.isNotNull(selection);
		Assert.isNotNull(node);

		List<ICategory> categories = new ArrayList<ICategory>();

		// Get all tree pathes of the given node
		if (selection instanceof ITreeSelection) {
			TreePath[] pathes = ((ITreeSelection)selection).getPathsFor(node);
			for (TreePath path : pathes) {
				// Loop through the parent pathes to find the category element
				TreePath parentPath = path.getParentPath();
				while (parentPath != null) {
					if (parentPath.getLastSegment() instanceof ICategory
									&& !categories.contains(parentPath.getLastSegment())) {
						categories.add((ICategory)parentPath.getLastSegment());
						break;
					}
					parentPath = parentPath.getParentPath();
				}
			}
		}

		return categories.toArray(new ICategory[categories.size()]);
	}

	/**
	 * Internal helper class to describe the delete operation to perform.
	 */
	private static class Operation {
		// The operation types
		public enum TYPE { Remove, Unlink }

		// The element to operate on
		public IPeerModel node;
		// The operation type to perform
		public TYPE type;
		// In case of an "unlink" operation, the parent category
		// is required.
		public ICategory parentCategory;

		/**
		 * Constructor.
		 */
		public Operation() {
		}

		/**
		 * Executes the operation.
		 *
		 * @throws Exception if the operation fails.
		 */
		public void execute() throws Exception {
			Assert.isNotNull(node);
			Assert.isNotNull(type);

			if (TYPE.Remove.equals(type)) {
				IURIPersistenceService service = ServiceManager.getInstance().getService(IURIPersistenceService.class);
				if (service == null) {
					throw new IOException("Persistence service instance unavailable."); //$NON-NLS-1$
				}
				service.delete(node, null);
			}
			else if (TYPE.Unlink.equals(type)) {
				Assert.isNotNull(parentCategory);

				ICategoryManager manager = Managers.getCategoryManager();
				Assert.isNotNull(manager);

				ICategorizable categorizable = (ICategorizable)node.getAdapter(ICategorizable.class);
				if (categorizable == null) {
					categorizable = (ICategorizable)Platform.getAdapterManager().getAdapter(node, ICategorizable.class);
				}
				Assert.isNotNull(categorizable);

				manager.remove(parentCategory.getId(), categorizable.getId());
			}
		}
	}

	/**
	 * Deletes all elements from the given selection and invokes the
	 * given callback once done.
	 *
	 * @param selection The selection. Must not be <code>null</code>.
	 * @param callback The callback. Must not be <code>null</code>.
	 */
	public void delete(final ISelection selection, final ICallback callback) {
		Assert.isNotNull(selection);
		Assert.isNotNull(callback);

		// The callback needs to be invoked in any case. However, if called
		// from an asynchronous callback, set this flag to false.
		boolean invokeCallback = true;

		// The selection must be a structured selection and must not be empty
		if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
			// Determine the operations to perform for each of the selected elements
			Operation[] operations = selection2operations((IStructuredSelection)selection);

			// Seek confirmation for the "remove" operations. If the user deny it,
			// everything, including the "unlink" operations are cancelled.
			boolean confirmed = confirmDelete(operations);

			// Execute the operations
			if (confirmed) {
				// If one of the operation is a "remove" operation, the locator
				// model needs to be refreshed
				boolean refreshModel = false;

				try {
					for (Operation op : operations) {
						if (Operation.TYPE.Remove.equals(op.type)) {
							refreshModel = true;
						}
						op.execute();
					}
				} catch (Exception e) {
					String template = NLS.bind(Messages.DeleteHandler_error_deleteFailed, Messages.PossibleCause);
					StatusHandlerUtil.handleStatus(StatusHelper.getStatus(e), selection, template,
													Messages.DeleteHandler_error_title, IContextHelpIds.MESSAGE_DELETE_FAILED, this, null);
				}

				if (refreshModel) {
					// Trigger a refresh of the model
					invokeCallback = false;
					Protocol.invokeLater(new Runnable() {
						@Override
						public void run() {
							ILocatorModelRefreshService service = Model.getModel().getService(ILocatorModelRefreshService.class);
							// Refresh the model now (must be executed within the TCF dispatch thread)
							if (service != null) service.refresh(new Callback() {
								@Override
								protected void internalDone(Object caller, IStatus status) {
									// Invoke the callback
									callback.done(DeleteHandler.this, Status.OK_STATUS);
								}
							});
						}
					});
				}
			}
		}

		if (invokeCallback) {
			callback.done(this, Status.OK_STATUS);
		}
	}

	/**
	 * Analyze the given selection and convert it to an list of operations
	 * to perform.
	 *
	 * @param selection The selection. Must not be <code>null</code>.
	 * @return The list of operations.
	 */
	private Operation[] selection2operations(IStructuredSelection selection) {
		Assert.isNotNull(selection);

		List<Operation> operations = new ArrayList<Operation>();

		Iterator<?> iterator = selection.iterator();
		while (iterator.hasNext()) {
			Object element = iterator.next();
			Assert.isTrue(element instanceof IPeerModel);
			IPeerModel node = (IPeerModel)element;

			boolean isStatic = isStatic(node);
			ICategory[] categories = getParentCategories(selection, node);

			if (categories.length == 0 && isStatic) {
				Operation op = new Operation();
				op.node = node;
				op.type = Operation.TYPE.Remove;
				operations.add(op);
			} else {
				for (ICategory category : categories) {
					// If the parent category is "Favorites", it is always
					// an "unlink" operation
					if (IUIConstants.ID_CAT_FAVORITES.equals(category.getId())) {
						Operation op = new Operation();
						op.node = node;
						op.type = Operation.TYPE.Unlink;
						op.parentCategory = category;
						operations.add(op);
					}
					// If the parent category is "My Targets", is is an
					// "remove" operation for static peers and "unlink" for
					// dynamic peers
					else if (IUIConstants.ID_CAT_MY_TARGETS.equals(category.getId())) {
						Operation op = new Operation();
						op.node = node;

						if (isStatic) {
							op.type = Operation.TYPE.Remove;
						} else {
							op.type = Operation.TYPE.Unlink;
							op.parentCategory = category;
						}

						operations.add(op);
					}
					else {
						Operation op = new Operation();
						op.node = node;
						op.type = Operation.TYPE.Remove;

						operations.add(op);
					}
				}
			}
		}

		return operations.toArray(new Operation[operations.size()]);
	}

	/**
	 * Confirm the deletion with the user.
	 *
	 * @param state The state of delegation handler.
	 * @return true if the user agrees to delete or it has confirmed previously.
	 */
	private boolean confirmDelete(Operation[] operations) {
		Assert.isNotNull(operations);

		boolean confirmed = false;

		// Find all elements to remove
		List<Operation> toRemove = new ArrayList<Operation>();
		for (Operation op : operations) {
			if (Operation.TYPE.Remove.equals(op.type)) {
				toRemove.add(op);
			}
		}

		// If there are node to remove -> ask for confirmation
		if (!toRemove.isEmpty()) {
			String question = getConfirmQuestion(toRemove);
			Shell parent = shell != null ? shell : PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
			confirmed = MessageDialog.openQuestion(parent, Messages.DeleteHandlerDelegate_DialogTitle, question);
		} else {
			confirmed = true;
		}

		return confirmed;
	}

	/**
	 * Get confirmation question displayed in the confirmation dialog.
	 *
	 * @param toRemove The list of nodes to remove.
	 * @return The question to ask the user.
	 */
	private String getConfirmQuestion(List<Operation> toRemove) {
		Assert.isNotNull(toRemove);

		String question;
		if (toRemove.size() == 1) {
			final Operation op = toRemove.get(0);
			final AtomicReference<String> name = new AtomicReference<String>();

			Runnable runnable = new Runnable() {
				@Override
				public void run() {
					name.set(op.node.getPeer().getName());
				}
			};

			if (Protocol.isDispatchThread()) {
				runnable.run();
			}
			else {
				Protocol.invokeAndWait(runnable);
			}

			question = NLS.bind(Messages.DeleteHandlerDelegate_MsgDeleteOnePeer, name.get());
		}
		else {
			question = NLS.bind(Messages.DeleteHandlerDelegate_MsgDeleteMultiplePeers, Integer.valueOf(toRemove.size()));
		}
		return question;
	}
}

Back to the top