/*
* Copyright 2013 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.explorer.dynamodb;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.ui.WebLinkListener;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.LocalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.Projection;
public class CreateTableSecondPage extends WizardPage {
private final List<LocalSecondaryIndex> localSecondaryIndexes;
private final List<AttributeDefinition> localSecondaryIndexKeyAttributes;
private IndexTable<LocalSecondaryIndex> localSecondaryIndexesTable;
private final int MAX_NUM_LSI = 5;
private final List<GlobalSecondaryIndex> globalSecondaryIndexes;
private final List<List<AttributeDefinition>> globalSecondaryIndexKeyAttributes;
private IndexTable<GlobalSecondaryIndex> globalSecondaryIndexesTable;
private final int MAX_NUM_GSI = 5;
private CreateTableWizard wizard;
private final String OK_MESSAGE = "Add one or more Local/Global Secondary Indexes(optional)";
CreateTableSecondPage(CreateTableWizard wizard) {
super("Configure table");
setMessage(OK_MESSAGE);
setImageDescriptor(AwsToolkitCore.getDefault().getImageRegistry().getDescriptor(AwsToolkitCore.IMAGE_AWS_LOGO));
localSecondaryIndexes = new LinkedList<LocalSecondaryIndex>();
wizard.getDataModel().setLocalSecondaryIndexes(localSecondaryIndexes);
localSecondaryIndexKeyAttributes = new LinkedList<AttributeDefinition>();
globalSecondaryIndexes = new LinkedList<GlobalSecondaryIndex>();
wizard.getDataModel().setGlobalSecondaryIndexes(globalSecondaryIndexes);
globalSecondaryIndexKeyAttributes = new LinkedList<List<AttributeDefinition>>();
this.wizard = wizard;
}
public void createControl(Composite parent) {
Composite comp = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(comp);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(comp);
createLinkSection(comp);
// LSI
Group lsiGroup = CreateTablePageUtil.newGroup(comp, "Local Secondary Index", 1);
GridDataFactory.fillDefaults().grab(true, true).applyTo(lsiGroup);
Button addLSIButton = new Button(lsiGroup, SWT.PUSH);
addLSIButton.setText("Add Local Secondary Index");
addLSIButton.setImage(AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_ADD));
addLSIButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (localSecondaryIndexes.size() >= MAX_NUM_LSI) {
new MessageDialog(getShell(), "Validation Error", null, "A table cannot have more than five local secondary indexes.", MessageDialog.ERROR, new String[] { "OK", "CANCEL" }, 0).open();
return;
}
AddLSIDialog addLSIDialog = new AddLSIDialog(Display.getCurrent().getActiveShell(), wizard.getDataModel());
if (addLSIDialog.open() == 0) {
// Add the LSI schema object
localSecondaryIndexes.add(addLSIDialog.getLocalSecondaryIndex());
// Add the key attribute definitions associated with this LSI
localSecondaryIndexKeyAttributes.add(addLSIDialog.getIndexRangeKeyAttributeDefinition());
wizard.collectAllAttribtueDefinitions();
localSecondaryIndexesTable.refresh();
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
});
// LSI label provider
IndexTable.IndexTableLabelProvider localSecondaryIndexTableLabelProvider = new IndexTable.IndexTableLabelProvider() {
@Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof LocalSecondaryIndex == false)
return "";
LocalSecondaryIndex index = (LocalSecondaryIndex) element;
switch (columnIndex) {
case 0: // index name
return index.getIndexName();
case 1: // index range key
String returnString = "";
returnString += index.getKeySchema().get(1).getAttributeName() + " (";
returnString += findAttributeType(index.getKeySchema().get(1).getAttributeName()) + ")";
return returnString;
case 2: // index projection
return IndexTable.getProjectionAttributes(
index.getProjection(),
index.getKeySchema(),
wizard.getDataModel());
}
return element.toString();
}
};
localSecondaryIndexesTable = new IndexTable<LocalSecondaryIndex>(
lsiGroup,
localSecondaryIndexes,
localSecondaryIndexTableLabelProvider) {
@Override
protected void createColumns(TableColumnLayout columnLayout, Table table) {
createColumn(table, columnLayout, "Index Name");
createColumn(table, columnLayout, "Attribute To Index");
createColumn(table, columnLayout, "Projected Attributes");
}
@Override
protected void onRemoveItem(int removed) {
localSecondaryIndexKeyAttributes.remove(removed);
wizard.collectAllAttribtueDefinitions();
}
};
GridDataFactory.fillDefaults().grab(true, true).applyTo(localSecondaryIndexesTable);
// GSI
Group gsiGroup = CreateTablePageUtil.newGroup(comp, "Global Secondary Index", 1);
GridDataFactory.fillDefaults().grab(true, true).applyTo(gsiGroup);
Button addGSIButton = new Button(gsiGroup, SWT.PUSH);
addGSIButton.setText("Add Global Secondary Index");
addGSIButton.setImage(AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_ADD));
addGSIButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (globalSecondaryIndexes.size() >= MAX_NUM_GSI) {
new MessageDialog(getShell(), "Validation Error", null, "A table cannot have more than five global secondary indexes.", MessageDialog.ERROR, new String[] { "OK", "CANCEL" }, 0).open();
return;
}
AddGSIDialog addGSIDialog = new AddGSIDialog(Display.getCurrent().getActiveShell(), wizard.getDataModel());
if (addGSIDialog.open() == 0) {
globalSecondaryIndexes.add(addGSIDialog.getGlobalSecondaryIndex());
globalSecondaryIndexKeyAttributes.add(addGSIDialog.getIndexKeyAttributeDefinitions());
wizard.collectAllAttribtueDefinitions();
globalSecondaryIndexesTable.refresh();
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
});
// GSI label provider
IndexTable.IndexTableLabelProvider globalSecondaryIndexTableLabelProvider = new IndexTable.IndexTableLabelProvider() {
@Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof GlobalSecondaryIndex == false)
return "";
GlobalSecondaryIndex index = (GlobalSecondaryIndex) element;
switch (columnIndex) {
case 0: // index name
return index.getIndexName();
case 1: // index hash
String returnString = "";
returnString += index.getKeySchema().get(0).getAttributeName() + " (";
returnString += findAttributeType(index.getKeySchema().get(0).getAttributeName()) + ")";
return returnString;
case 2: // index range
returnString = "";
if (index.getKeySchema().size() > 1) {
returnString += index.getKeySchema().get(1).getAttributeName() + " (";
returnString += findAttributeType(index.getKeySchema().get(1).getAttributeName()) + ")";
}
return returnString;
case 3: // index projection
return IndexTable.getProjectionAttributes(
index.getProjection(),
index.getKeySchema(),
wizard.getDataModel());
case 4: // index throughput
return "Read : " + index.getProvisionedThroughput().getReadCapacityUnits()
+ ", Write : " + index.getProvisionedThroughput().getWriteCapacityUnits();
default:
return "";
}
}
};
globalSecondaryIndexesTable = new IndexTable<GlobalSecondaryIndex>(
gsiGroup,
globalSecondaryIndexes,
globalSecondaryIndexTableLabelProvider) {
@Override
protected void createColumns(TableColumnLayout columnLayout, Table table) {
createColumn(table, columnLayout, "Index Name");
createColumn(table, columnLayout, "Index Hash Key");
createColumn(table, columnLayout, "Index Range Key");
createColumn(table, columnLayout, "Projected Attributes");
createColumn(table, columnLayout, "Provisioned Throughput");
}
@Override
protected void onRemoveItem(int removed) {
globalSecondaryIndexKeyAttributes.remove(removed);
wizard.collectAllAttribtueDefinitions();
}
};
GridDataFactory.fillDefaults().grab(true, true).applyTo(globalSecondaryIndexesTable);
setControl(comp);
}
public void createLinkSection(Composite parent) {
String ConceptUrl1 = "http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html";
String ConceptUrl2 = "http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html";
Link link = new Link(parent, SWT.NONE | SWT.WRAP);
link.setText("You can only define local/global secondary indexes at table creation time, and cannot remove them or modify the index key schema later. "
+ "Local/global secondary indexes are not appropriate for every application; see " + "<a href=\"" + ConceptUrl1 + "\">Local Secondary Indexes</a> and <a href=\"" + ConceptUrl2
+ "\">Global Secondary Indexes</a>.\n");
link.addListener(SWT.Selection, new WebLinkListener());
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
gridData.widthHint = 200;
link.setLayoutData(gridData);
}
public Set<AttributeDefinition> getAllIndexKeyAttributeDefinitions() {
Set<AttributeDefinition> allAttrs = new HashSet<AttributeDefinition>();
for (AttributeDefinition lsiKey : localSecondaryIndexKeyAttributes) {
allAttrs.add(lsiKey);
}
for (List<AttributeDefinition> gsiKey : globalSecondaryIndexKeyAttributes) {
allAttrs.addAll(gsiKey);
}
return allAttrs;
}
private String findAttributeType(String attributeName) {
for (AttributeDefinition attribute : wizard.getDataModel().getAttributeDefinitions()) {
if (attribute.getAttributeName().equals(attributeName)) {
return attribute.getAttributeType();
}
}
return "";
}
/** Abstract base class that could be customized for LSI/GSI */
private static abstract class IndexTable <T> extends Composite {
private final List<T> indexes;
private final TableViewer viewer;
IndexTable(Composite parent,
final List<T> indexes,
final IndexTableLabelProvider labelProvider) {
super(parent, SWT.NONE);
this.indexes = indexes;
TableColumnLayout tableColumnLayout = new TableColumnLayout();
this.setLayout(tableColumnLayout);
viewer = new TableViewer(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.getTable().setLinesVisible(true);
viewer.getTable().setHeaderVisible(true);
viewer.setLabelProvider(labelProvider);
viewer.setContentProvider(new IndexTableContentProvider());
createColumns(tableColumnLayout, viewer.getTable());
MenuManager menuManager = new MenuManager("#PopupMenu");
menuManager.setRemoveAllWhenShown(true);
menuManager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
if (viewer.getTable().getSelectionCount() > 0) {
manager.add(new Action() {
@Override
public ImageDescriptor getImageDescriptor() {
return AwsToolkitCore.getDefault().getImageRegistry().getDescriptor(AwsToolkitCore.IMAGE_REMOVE);
}
@Override
public void run() {
int selected = viewer.getTable().getSelectionIndex();
IndexTable.this.indexes.remove(selected);
IndexTable.this.onRemoveItem(selected);
refresh();
}
@Override
public String getText() {
return "Delete Index";
}
});
}
}
});
viewer.getTable().setMenu(menuManager.createContextMenu(viewer.getTable()));
}
protected abstract void createColumns(TableColumnLayout columnLayout, Table table);
/**
* Callback for any additional operations to be executed after an item
* in the table is removed.
*/
protected abstract void onRemoveItem(int removed);
protected TableColumn createColumn(Table table, TableColumnLayout columnLayout, String text) {
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText(text);
column.setMoveable(true);
columnLayout.setColumnData(column, new ColumnPixelData(150));
return column;
}
protected static abstract class IndexTableLabelProvider implements ITableLabelProvider {
public void addListener(ILabelProviderListener listener) {
}
public void removeListener(ILabelProviderListener listener) {
}
public void dispose() {
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
public abstract String getColumnText(Object element, int columnIndex);
}
/** Shared by LSI and GSI table */
private final class IndexTableContentProvider extends ArrayContentProvider {
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
@Override
public Object[] getElements(Object inputElement) {
return indexes.toArray();
}
}
/** Enforce call getElement method in contentProvider */
private void refresh() {
viewer.setInput(new Object());
}
/** Generate a String has the detail info about the project attribute. */
private static String getProjectionAttributes(Projection indexProjection, List<KeySchemaElement> indexKeys, CreateTableDataModel dataModel) {
String returnString = "";
String DELIM = ", ";
if (indexProjection.getProjectionType().equals("All Attributes")) {
return indexProjection.getProjectionType();
} else if (indexProjection.getProjectionType().equals("Specify Attributes")) {
return CreateTablePageUtil.stringJoin(indexProjection.getNonKeyAttributes(), DELIM);
} else {
// Primary keys
Set<String> projectedKeys = new HashSet<String>();
returnString += dataModel.getHashKeyName() + DELIM;
projectedKeys.add(dataModel.getHashKeyName());
if (dataModel.getEnableRangeKey()) {
returnString += dataModel.getRangeKeyName() + DELIM;
projectedKeys.add(dataModel.getRangeKeyName());
}
// Index keys (we should not include index keys that are already part of the primary key)
for (KeySchemaElement indexKey : indexKeys) {
String indexKeyName = indexKey.getAttributeName();
if ( !projectedKeys.contains(indexKeyName) ) {
returnString += indexKeyName + DELIM;
projectedKeys.add(indexKeyName);
}
}
return returnString.substring(0, returnString.length() - DELIM.length());
}
}
}
}