/*
* Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. 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.lambda.upload.wizard.page;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newCombo;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newControlDecoration;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newFillingLabel;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newGroup;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newRadioButton;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.trackRegionComboChangeSelection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.WritableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Text;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.core.regions.RegionUtils;
import com.amazonaws.eclipse.core.regions.ServiceAbbreviations;
import com.amazonaws.eclipse.core.ui.CancelableThread;
import com.amazonaws.eclipse.databinding.BooleanValidator;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.databinding.NotEmptyValidator;
import com.amazonaws.eclipse.databinding.NotInListValidator;
import com.amazonaws.eclipse.lambda.LambdaPlugin;
import com.amazonaws.eclipse.lambda.ServiceApiUtils;
import com.amazonaws.eclipse.lambda.upload.wizard.model.UploadFunctionWizardDataModel;
import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.model.FunctionConfiguration;
public class TargetFunctionSelectionPage extends WizardPageWithOnEnterHook {
/* Data model and binding */
private final UploadFunctionWizardDataModel dataModel;
private final DataBindingContext bindingContext;
private final AggregateValidationStatus aggregateValidationStatus;
/* UI widgets */
// Select region
private Combo regionCombo;
// Select Java function
private IObservableValue existingJavaFunctionLoaded = new WritableValue();
private IObservableSet existingJavaFunctionNames = new WritableSet();
private Button useExistingJavaFunctionRadioButton;
private ISWTObservableValue useExistingJavaFunctionRadioButtonObservable;
private Combo existingJavaFunctionNameCombo;
private Button createNewJavaFunctionRadioButton;
private ISWTObservableValue createNewJavaFunctionRadioButtonObservable;
private Text newJavaFunctionNameText;
private ControlDecoration newJavaFunctionNameDecoration;
private ISWTObservableValue newJavaFunctionNameTextObservable;
private final SelectionListener javaFunctionSectionRadioButtonSelectionListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
radioButtonSelected(e.getSource());
runValidators();
}
};
/* Other */
private AWSLambda lambdaClient;
private LoadJavaFunctionsThread loadJavaFunctionsThread;
/* Constants */
private static final String LOADING = "Loading...";
private static final String NONE_FOUND = "None found";
public TargetFunctionSelectionPage(UploadFunctionWizardDataModel dataModel) {
super("Select Target Lambda Function");
setTitle("Select Target Lambda Function");
setDescription("");
setPageComplete(false);
this.dataModel = dataModel;
this.bindingContext = new DataBindingContext();
this.aggregateValidationStatus = new AggregateValidationStatus(
bindingContext, AggregateValidationStatus.MAX_SEVERITY);
}
public void createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayout(new GridLayout(1, false));
createRegionSection(composite);
createJavaFunctionSection(composite);
bindControls();
initializeValidators();
initializeDefaults();
// Force refresh function list
onRegionSelectionChange();
setControl(composite);
setPageComplete(false);
}
private void createRegionSection(Composite composite) {
Group regionGroup = newGroup(composite, "Select AWS Region");
regionGroup.setLayout(new GridLayout(1, false));
newFillingLabel(regionGroup,
"Select the AWS region where your Lambda function is created.");
Region initialRegion = null;
Region lastDeploymentRegion = getLastDeploymentRegion();
regionCombo = newCombo(regionGroup);
for (Region region : RegionUtils.getRegionsForService(ServiceAbbreviations.LAMBDA)) {
regionCombo.add(region.getName());
regionCombo.setData(region.getName(), region);
if (region.equals(lastDeploymentRegion)) {
initialRegion = region;
}
}
// Find the default region selection
if (initialRegion == null) {
if ( RegionUtils.isServiceSupportedInCurrentRegion(ServiceAbbreviations.LAMBDA) ) {
initialRegion = RegionUtils.getCurrentRegion();
} else {
initialRegion = RegionUtils.getRegion(LambdaPlugin.DEFAULT_REGION);
}
}
regionCombo.setText(initialRegion.getName());
regionCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
trackRegionComboChangeSelection();
onRegionSelectionChange();
}
});
}
private Region getLastDeploymentRegion() {
return this.dataModel.getProjectMetadataBeforeUpload() == null
? null
: this.dataModel.getProjectMetadataBeforeUpload()
.getLastDeploymentRegion();
}
private void createJavaFunctionSection(Composite composite) {
Group javaFunctionGroup = newGroup(composite, "Select or create a Lambda function:");
javaFunctionGroup.setLayout(new GridLayout(2, false));
useExistingJavaFunctionRadioButton = newRadioButton(javaFunctionGroup,
"Choose an existing Lambda function:", 1, true,
javaFunctionSectionRadioButtonSelectionListener);
existingJavaFunctionNameCombo = newCombo(javaFunctionGroup);
existingJavaFunctionNameCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onExistingJavaFunctionSelectionChange();
}
});
createNewJavaFunctionRadioButton = newRadioButton(javaFunctionGroup,
"Create a new Lambda function:", 1, true,
javaFunctionSectionRadioButtonSelectionListener);
newJavaFunctionNameText = newText(javaFunctionGroup);
newJavaFunctionNameDecoration = newControlDecoration(
newJavaFunctionNameText,
"Enter a new function name or select an existing function.");
}
private void onRegionSelectionChange() {
Region region = (Region)regionCombo.getData(regionCombo.getText());
String endpoint = region.getServiceEndpoints()
.get(ServiceAbbreviations.LAMBDA);
lambdaClient = AwsToolkitCore.getClientFactory()
.getLambdaClientByEndpoint(endpoint);
dataModel.setRegion(region);
refreshFunctions();
}
private void onExistingJavaFunctionSelectionChange() {
if (existingJavaFunctionLoaded.getValue().equals(Boolean.TRUE)) {
Object selectedData = existingJavaFunctionNameCombo
.getData(existingJavaFunctionNameCombo.getText());
FunctionConfiguration selectedFunction = (FunctionConfiguration)selectedData;
dataModel.setExistingFunction(selectedFunction);
}
}
private void refreshFunctions() {
existingJavaFunctionLoaded.setValue(false);
existingJavaFunctionNames.clear();
if (existingJavaFunctionNameCombo != null) {
existingJavaFunctionNameCombo.setItems(new String[] {LOADING});
existingJavaFunctionNameCombo.select(0);
}
CancelableThread.cancelThread(loadJavaFunctionsThread);
loadJavaFunctionsThread = new LoadJavaFunctionsThread(
getLastDeploymentFunctionName());
loadJavaFunctionsThread.start();
}
private String getLastDeploymentFunctionName() {
return this.dataModel.getProjectMetadataBeforeUpload() == null
? null
: this.dataModel.getProjectMetadataBeforeUpload()
.getLastDeploymentFunctionName();
}
private void radioButtonSelected(Object source) {
if ( source == useExistingJavaFunctionRadioButton || source == createNewJavaFunctionRadioButton) {
boolean isCreatingNewFunction = (Boolean) createNewJavaFunctionRadioButtonObservable.getValue();
existingJavaFunctionNameCombo.setEnabled(!isCreatingNewFunction);
newJavaFunctionNameText.setEnabled(isCreatingNewFunction);
}
}
private void bindControls() {
useExistingJavaFunctionRadioButtonObservable = SWTObservables
.observeSelection(useExistingJavaFunctionRadioButton);
createNewJavaFunctionRadioButtonObservable = SWTObservables
.observeSelection(createNewJavaFunctionRadioButton);
bindingContext.bindValue(
createNewJavaFunctionRadioButtonObservable,
PojoObservables.observeValue(
dataModel,
UploadFunctionWizardDataModel.P_IS_CREATING_NEW_FUNCTION));
newJavaFunctionNameTextObservable = SWTObservables
.observeText(newJavaFunctionNameText, SWT.Modify);
bindingContext.bindValue(
newJavaFunctionNameTextObservable,
PojoObservables.observeValue(
dataModel,
UploadFunctionWizardDataModel.P_NEW_FUNCTION_NAME));
}
private void initializeValidators() {
// Bind the validation status to the wizard page message
aggregateValidationStatus.addChangeListener(new IChangeListener() {
public void handleChange(ChangeEvent arg0) {
Object value = aggregateValidationStatus.getValue();
if (value instanceof IStatus == false) return;
IStatus status = (IStatus)value;
boolean success = (status.getSeverity() == IStatus.OK);
setPageComplete(success);
if (success) {
setMessage("", IStatus.OK);
} else {
setMessage(status.getMessage(), IStatus.ERROR);
}
}
});
// Validation status providers
bindingContext.addValidationStatusProvider(new ChainValidator<Boolean>(
existingJavaFunctionLoaded,
useExistingJavaFunctionRadioButtonObservable, // enabler
new BooleanValidator("Please select a Lambda function")));
ChainValidator<String> functionNameValidator = new ChainValidator<String>(
newJavaFunctionNameTextObservable,
createNewJavaFunctionRadioButtonObservable, // enabler
new NotEmptyValidator("Please provide a Lambda function name"),
new NotInListValidator<String>(existingJavaFunctionNames, "Duplidate Lambda function name"));
bindingContext.addValidationStatusProvider(functionNameValidator);
new DecorationChangeListener(newJavaFunctionNameDecoration,
functionNameValidator.getValidationStatus());
}
private void runValidators() {
Iterator<?> iterator = bindingContext.getBindings().iterator();
while (iterator.hasNext()) {
Binding binding = (Binding)iterator.next();
binding.updateTargetToModel();
}
}
private void initializeDefaults() {
existingJavaFunctionLoaded.setValue(false);
useExistingJavaFunctionRadioButtonObservable.setValue(true);
createNewJavaFunctionRadioButtonObservable.setValue(false);
newJavaFunctionNameTextObservable.setValue("MyFunction");
radioButtonSelected(useExistingJavaFunctionRadioButton);
}
private final class LoadJavaFunctionsThread extends CancelableThread {
private final String defaultFunctionName;
/**
* @param defaultFunctionName
* the function that should be selected by default after all
* functions are loaded.
*/
LoadJavaFunctionsThread(String defaultFunctionName) {
this.defaultFunctionName = defaultFunctionName;
}
@Override
public void run() {
final List<String> javaFunctionNames = new ArrayList<String>();
final Map<String, FunctionConfiguration> javaFunctions = new HashMap<String, FunctionConfiguration>();
try {
for (FunctionConfiguration funcConfig : ServiceApiUtils.getAllJavaFunctions(lambdaClient)) {
javaFunctionNames.add(funcConfig.getFunctionName());
javaFunctions.put(funcConfig.getFunctionName(), funcConfig);
}
// Sort by name
Collections.sort(javaFunctionNames);
} catch (Exception e) {
LambdaPlugin.getDefault().reportException(
"Unable to load existing Java functions.", e);
setRunning(false);
return;
}
Display.getDefault().asyncExec(new Runnable() {
public void run() {
try {
synchronized (LoadJavaFunctionsThread.this) {
if ( !isCanceled() ) {
existingJavaFunctionNameCombo.removeAll();
if (javaFunctionNames.size() > 0) {
existingJavaFunctionNames.clear();
existingJavaFunctionNames.addAll(javaFunctionNames);
existingJavaFunctionLoaded.setValue(true);
for ( String funcName : javaFunctionNames ) {
existingJavaFunctionNameCombo.add(funcName);
existingJavaFunctionNameCombo.setData(
funcName, javaFunctions.get(funcName));
}
useExistingJavaFunctionRadioButton.setEnabled(true);
existingJavaFunctionNameCombo.setEnabled(true);
existingJavaFunctionNameCombo
.select(findDefaultFunction(javaFunctionNames));
onExistingJavaFunctionSelectionChange();
} else {
existingJavaFunctionLoaded.setValue(false);
useExistingJavaFunctionRadioButton.setEnabled(false);
existingJavaFunctionNameCombo.setEnabled(false);
existingJavaFunctionNameCombo.setItems(new String[] { NONE_FOUND});
existingJavaFunctionNameCombo.select(0);
useExistingJavaFunctionRadioButtonObservable.setValue(false);
createNewJavaFunctionRadioButtonObservable.setValue(true);
radioButtonSelected(useExistingJavaFunctionRadioButton);
}
// Re-calculate UI layout
((Composite)getControl()).layout();
}
}
} finally {
setRunning(false);
}
}
});
}
private int findDefaultFunction(List<String> functionNames) {
if (this.defaultFunctionName == null) {
return 0;
}
int defaultInd = functionNames.indexOf(this.defaultFunctionName);
return defaultInd < 0 ? 0 : defaultInd;
}
}
@Override
protected void onEnterPage() {
}
}