/*
* Copyright 2008-2012 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and
* limitations under the License.
*/
package com.amazonaws.eclipse.ec2.ui.amis;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ILazyTreeContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.statushandlers.StatusManager;
import com.amazonaws.eclipse.core.AccountInfo;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.ec2.Ec2Plugin;
import com.amazonaws.eclipse.ec2.TagFormatter;
import com.amazonaws.eclipse.ec2.ui.SelectionTable;
import com.amazonaws.eclipse.ec2.ui.launchwizard.LaunchWizard;
import com.amazonaws.eclipse.ec2.utils.IMenu;
import com.amazonaws.eclipse.ec2.utils.MenuAction;
import com.amazonaws.eclipse.ec2.utils.MenuHandler;
import com.amazonaws.services.ec2.model.DeregisterImageRequest;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Image;
/**
* Selection table for AMIs.
*/
public class AmiSelectionTable extends SelectionTable implements IMenu {
/** Dropdown filter menu for AMIs */
private IAction amiFilterDropDownAction;
/** DropDown menu handler for AMI Filter Items*/
private MenuHandler amiDropDownMenuHandler;
/** Dropdown filter menu for Platforms */
private IAction platformFilterDropDownAction;
/** DropDown menu handler for Platform Filter Items*/
private MenuHandler platformDropDownMenuHandler;
/** Holds the number of AMIs currently display */
private int noOfAMIs;
/** An action object to launch instances of the selected AMI */
private Action launchAction;
/** An action object to refresh the AMI list */
private Action refreshAction;
/** An action object to delete a selected AMI */
private Action deleteAmiAction;
/** The user's account info */
private static AccountInfo accountInfo = AwsToolkitCore.getDefault().getAccountInfo();
/** The content provider for the data displayed in this selection table */
private ViewContentProvider contentProvider = new ViewContentProvider();
private LoadImageDescriptionsThread loadImageThread;
/* Column identifiers */
private static final int IMAGE_ID_COLUMN = 0;
private static final int IMAGE_MANIFEST_COLUMN = 1;
private static final int IMAGE_STATE_COLUMN = 2;
private static final int IMAGE_OWNER_COLUMN = 3;
private static final int IMAGE_TAGS_COLUMN = 4;
/**
* Creates a new AMI selection table with the specified parent.
*
* @param parent
* The parent of this new selection table.
* @param listener
* The selection table listener object that should be notified
* when this selection table loads data.
*/
public AmiSelectionTable(Composite parent, SelectionTableListener listener) {
super(parent, false, true);
viewer.setContentProvider(contentProvider);
viewer.setLabelProvider(new ViewLabelProvider());
this.setListener(listener);
createToolbarActions();
refreshAmis();
viewer.getTree().addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e) {
Image image = getSelectedImage();
if (image == null) return;
launchAction.run();
}
});
}
/**
* Refreshes the set of Amis asynchronously.
*/
private void refreshAmis() {
cancelLoadAmisThread();
loadImageThread = new LoadImageDescriptionsThread();
loadImageThread.start();
}
private void cancelLoadAmisThread() {
if ( loadImageThread != null ) {
synchronized (loadImageThread) {
if ( !loadImageThread.canceled ) {
loadImageThread.cancel();
}
}
}
}
@Override
public void dispose() {
cancelLoadAmisThread();
super.dispose();
}
private void createToolbarActions() {
amiDropDownMenuHandler = new MenuHandler();
amiDropDownMenuHandler.addListener(this);
amiDropDownMenuHandler.add("ALL", "All Images");
amiDropDownMenuHandler.add("amazon", "Amazon Images", true);
amiDropDownMenuHandler.add("Public", "Public Images");
amiDropDownMenuHandler.add("Private", "Private Images");
amiDropDownMenuHandler.add("ByMe", "Owned By Me");
amiDropDownMenuHandler.add("32-bit", "32-bit");
amiDropDownMenuHandler.add("64-bit", "64-bit");
amiFilterDropDownAction = new MenuAction("AMI Filter", "Filter AMIs", "filter", amiDropDownMenuHandler);
platformDropDownMenuHandler = new MenuHandler();
platformDropDownMenuHandler.addListener(this);
platformDropDownMenuHandler.add("ALL", "All Platforms", true);
platformDropDownMenuHandler.add("windows", "Windows");
platformFilterDropDownAction = new MenuAction("Platform Filter", "Filter by platform", "filter", platformDropDownMenuHandler);
refreshAction = new Action() {
public void run() {
refreshAmis();
}
};
refreshAction.setText("Refresh");
refreshAction.setDescription("Refresh the list of images");
refreshAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("refresh"));
}
/**
* Returns an action object that refreshes the AMI selection table.
*
* @return An action object that refreshes the AMI selection table.
*/
public Action getRefreshAction() {
return refreshAction;
}
/**
* Returns the Action object that shows the AMI filter dropdown menus
*
* @return The IAction object that shows the AMI filter dropdown menus
*/
public IAction getAmiFilterDropDownAction() {
return amiFilterDropDownAction;
}
/**
* Returns the Action object that shows the Platform filter dropdown menus
*
* @return The IAction object that shows the Platform filter dropdown menus
*/
public IAction getPlatformFilterDropDownAction() {
return platformFilterDropDownAction;
}
/**
* Filters the AMI list to those matching the specified string.
*
* @param searchText The text on which to filter.
*/
public void filterImages(String searchText) {
contentProvider.setFilter(searchText);
viewer.refresh();
}
/**
* Returns the selected AMI.
*
* @return The currently selected AMI, or null if none is selected.
*/
public Image getSelectedImage() {
return (Image)getSelection();
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#createColumns()
*/
@Override
protected void createColumns() {
newColumn("AMI ID", 10);
newColumn("Manifest", 20);
newColumn("State", 10);
newColumn("Owner", 10);
newColumn("Tags", 15);
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#fillContextMenu(org.eclipse.jface.action.IMenuManager)
*/
@Override
protected void fillContextMenu(IMenuManager manager) {
Image selectedImage = getSelectedImage();
launchAction.setEnabled(selectedImage != null);
deleteAmiAction.setEnabled(doesUserHavePermissionToDelete(selectedImage));
manager.add(refreshAction);
manager.add(new Separator());
manager.add(launchAction);
manager.add(new Separator());
manager.add(deleteAmiAction);
}
private boolean doesUserHavePermissionToDelete(Image ami) {
String userId = accountInfo.getUserId();
if (ami == null) return false;
if (userId == null) return false;
return ami.getOwnerId().equals(userId);
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#makeActions()
*/
@Override
protected void makeActions() {
launchAction = new Action() {
public void run() {
Image image = getSelectedImage();
new WizardDialog(Display.getCurrent().getActiveShell(), new LaunchWizard(image)).open();
}
};
launchAction.setText("Launch...");
launchAction.setToolTipText("Launch this image");
launchAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("launch"));
deleteAmiAction = new Action() {
public void run() {
MessageBox messageBox = new MessageBox(new Shell(), SWT.ICON_WARNING | SWT.OK | SWT.CANCEL);
messageBox.setText("Delete selected AMI?");
messageBox.setMessage("If you continue, you won't be able to use this AMI anymore.");
// Bail out if the user cancels...
if (messageBox.open() == SWT.CANCEL) return;
final Image image = getSelectedImage();
new DeleteAmiThread(image).start();
}
};
deleteAmiAction.setText("Delete AMI");
deleteAmiAction.setToolTipText("Delete the selected AMI");
deleteAmiAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("remove"));
}
private class ViewContentProvider implements ILazyTreeContentProvider {
private List<Image> unfilteredImages;
private List<Image> filteredImages;
private String filter;
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
public void inputChanged(Viewer v, Object oldInput, Object newInput) {
filterImages();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public void dispose() {}
protected void applyFilters() {
filterImages();
}
private void filterImages() {
noOfAMIs = 0; //Resets no of AMIs
synchronized (this) {
if (unfilteredImages == null) unfilteredImages = new ArrayList<Image>();
}
// We filter based on Text filter and Drop down Filters
String[] searchTerms = (filter == null ? null : filter.split(" "));
List<Image> tempFilteredImages = new ArrayList<Image>(unfilteredImages.size());
for (Image image : unfilteredImages) {
boolean containsAllTerms = true;
if(searchTerms != null) {
for (String searchTerm : searchTerms) {
String imageDescription = image.getImageId() + " " + image.getImageLocation() + image.getOwnerId() + image.getState();
if (!imageDescription.toLowerCase().contains(searchTerm.toLowerCase())) {
containsAllTerms = false;
}
}
}
if (containsAllTerms) {
tempFilteredImages.add(image);
}
}
filteredImages = tempFilteredImages;
noOfAMIs = filteredImages.size();
viewer.getTree().setItemCount(filteredImages.size());
if (selectionTableListener != null) selectionTableListener.finishedLoadingData(noOfAMIs);
}
/**
* Sets the filter used to control what content is returned.
*
* @param filter The filter to be applied when returning content.
*/
public void setFilter(String filter) {
this.filter = filter;
applyFilters();
}
public void updateChildCount(Object element, int currentChildCount) {
if (element instanceof Image){
viewer.setChildCount(element, 0);
} else {
viewer.setChildCount(element, filteredImages.size());
}
}
public void updateElement(Object parent, int index) {
Object element = filteredImages.get(index);
viewer.replace(parent, index, element);
updateChildCount(element, -1);
}
public void setUnfilteredImages(List<Image> unfilteredImages) {
this.unfilteredImages = unfilteredImages;
}
public Object getParent(Object element) {
return null;
}
}
private class ViewLabelProvider extends LabelProvider implements ITableLabelProvider {
public String getColumnText(Object obj, int index) {
if (obj == null) {
return "??";
}
Image image = (Image)obj;
switch (index) {
case IMAGE_ID_COLUMN:
return image.getImageId();
case IMAGE_MANIFEST_COLUMN:
return image.getImageLocation();
case IMAGE_STATE_COLUMN:
return image.getState();
case IMAGE_OWNER_COLUMN:
return image.getOwnerId();
case IMAGE_TAGS_COLUMN:
return TagFormatter.formatTags(image.getTags());
}
return "???";
}
public org.eclipse.swt.graphics.Image getColumnImage(Object obj, int index) {
if (index == 0)
return Ec2Plugin.getDefault().getImageRegistry().get("ami");
return null;
}
public org.eclipse.swt.graphics.Image getImage(Object obj) {
return null;
}
}
/*
* Private Thread subclasses for making EC2 service calls.
*/
/**
* Thread subclass for making an EC2 service call to delete an AMI.
*/
private class DeleteAmiThread extends Thread {
/** The AMI to delete */
private final Image image;
/**
* Creates a new thread ready to be started to delete the specified AMI.
*
* @param image
* The AMI to delete.
*/
public DeleteAmiThread(Image image) {
this.image = image;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
DeregisterImageRequest request = new DeregisterImageRequest();
request.setImageId(image.getImageId());
getAwsEc2Client().deregisterImage(request);
refreshAmis();
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, Ec2Plugin.PLUGIN_ID,
"Unable to delete AMI: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
}
}
/**
* Thread subclass for making EC2 service calls to load a list of AMIs.
*/
private class LoadImageDescriptionsThread extends Thread {
private boolean canceled = false;
private synchronized void cancel() {
canceled = true;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
enableActions(false);
if (selectionTableListener != null) selectionTableListener.loadingData();
try {
final List<Image> images = getImages();
synchronized (this) {
if ( !canceled ) {
noOfAMIs = images.size();
Display.getDefault().syncExec(new Runnable() {
public void run() {
if ( viewer != null ) {
// There appears to be a bug in SWT virtual
// trees (at least on some platforms) that
// can lead to a stack overflow when trying
// to preserve selection on an input change.
viewer.getTree().deselectAll();
contentProvider.setUnfilteredImages(images);
viewer.setInput(images);
contentProvider.applyFilters();
}
}
});
if (selectionTableListener != null) selectionTableListener.finishedLoadingData(noOfAMIs);
enableActions(true);
}
}
} catch (Exception e) {
// Only log an error if the account info is valid and we
// actually expected this call to work
if (AwsToolkitCore.getDefault().getAccountInfo().isValid()) {
Status status = new Status(IStatus.ERROR, Ec2Plugin.PLUGIN_ID,
"Unable to query list of AMIs: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.LOG);
}
if (selectionTableListener != null) selectionTableListener.finishedLoadingData(noOfAMIs);
enableActions(true);
}
}
/**
* Gets a list of images, filtering them according to the current filter
* control settings.
*/
public List<Image> getImages() {
DescribeImagesRequest request = new DescribeImagesRequest().withFilters(new LinkedList<Filter>());
request.getFilters().add(new Filter().withName("image-type").withValues("machine"));
String menuId = amiDropDownMenuHandler.getCurrentSelection().getMenuId();
if (!menuId.equals("ALL")) {
if (menuId.equals("amazon")) {
List<String> owners = new LinkedList<String>();
owners.add("amazon");
request.setOwners(owners);
} else if (menuId.equals("Public")) {
request.getFilters().add(new Filter().withName("is-public").withValues("true"));
} else if (menuId.equals("Private")) {
request.getFilters().add(new Filter().withName("is-public").withValues("false"));
} else if (menuId.equals("ByMe")) {
List<String> owners = new LinkedList<String>();
owners.add("self");
request.setOwners(owners);
} else if (menuId.equals("32-bit")) {
request.getFilters().add(new Filter().withName("architecture").withValues("i386"));
} else if (menuId.equals("64-bit")) {
request.getFilters().add(new Filter().withName("architecture").withValues("x86_64"));
}
}
if (!platformDropDownMenuHandler.getCurrentSelection().getMenuId().equals("ALL")) {
if (platformDropDownMenuHandler.getCurrentSelection().getMenuId().equals("windows")) {
request.getFilters().add(new Filter().withName("platform").withValues("windows"));
}
}
return getAwsEc2Client().describeImages(request).getImages();
}
}
/**
* Callback function. Is called from the DropdownMenuHandler when a menu
* option is clicked
*
* @see com.amazonaws.eclipse.ec2.utils.IMenu#menuClicked(com.amazonaws.eclipse.ec2.utils.IMenu.MenuItem)
*/
public void menuClicked(MenuItem itemSelected) {
refreshAmis();
}
/**
* Enables/Disables dropdown filters
*/
private void enableActions(boolean enabled) {
refreshAction.setEnabled(enabled);
amiFilterDropDownAction.setEnabled(enabled);
platformFilterDropDownAction.setEnabled(enabled);
}
}