/* * 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()); } } } }