/* * Copyright 2016 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.project.wizard.util; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newCheckbox; 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.newLink; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaHandlerType.REQUEST_HANDLER; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaHandlerType.STREAM_REQUEST_HANDLER; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_CUSTOM_HANDLER_INPUT_TYPE; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_HANDLER_CLASS_NAME; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_HANDLER_OUTPUT_TYPE; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_HANDLER_PACKAGE_NAME; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_HANDLER_TYPE; import static com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel.P_SHOW_README_FILE; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.StringWriter; import java.util.Iterator; 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.IChangeListener; 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.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer; import org.eclipse.jdt.internal.ui.text.SimpleJavaSourceViewerConfiguration; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.ui.text.IJavaPartitions; import org.eclipse.jdt.ui.text.JavaTextTools; import org.eclipse.jface.databinding.swt.ISWTObservableValue; import org.eclipse.jface.databinding.swt.SWTObservables; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.GridData; 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.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Text; 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.lambda.LambdaPlugin; import com.amazonaws.eclipse.lambda.UrlConstants; import com.amazonaws.eclipse.lambda.project.template.CodeTemplateManager; import com.amazonaws.eclipse.lambda.project.wizard.model.LambdaHandlerType; import com.amazonaws.eclipse.lambda.project.wizard.model.LambdaFunctionWizardDataModel; import com.amazonaws.eclipse.lambda.project.wizard.model.PredefinedHandlerInputType; import com.amazonaws.eclipse.lambda.project.wizard.page.validator.ValidPackageNameValidator; import com.amazonaws.eclipse.sdk.ui.JavaSdkManager; import freemarker.template.Template; @SuppressWarnings("restriction") public class LambdaFunctionGroup { private WizardPage parentWizard; private final LambdaFunctionWizardDataModel dataModel; private final DataBindingContext bindingContext; private final AggregateValidationStatus aggregateValidationStatus; /* Function handler section */ private Text handlerPackageText; private ControlDecoration handlerPackageTextDecoration; private ISWTObservableValue handlerPackageTextObservable; private Text handlerClassText; private ControlDecoration handlerClassTextDecoration; private ISWTObservableValue handlerClassTextObservable; private Combo handlerTypeCombo; private Link handlerTypeDescriptionLink; private ISWTObservableValue handlerTypeComboObservable; private Combo predefinedHandlerInputCombo; private Text customHandlerInputTypeText; private ControlDecoration customHandlerInputTypeTextDecoration; private ISWTObservableValue customHandlerInputTypeTextObservable; private IObservableValue enableCustomHandlerInputTypeValidation = new WritableValue(); private IObservableValue enableSdkInstalledValidation = new WritableValue(); private IObservableValue sdkInstalledObservable = new WritableValue(); private Text handlerOutputTypeText; private ControlDecoration handlerOutputTypeTextDecoration; private ISWTObservableValue handlerOutputTypeTextObservable; private JavaSourceViewer sourcePreview; private Document sourcePreviewDocument; private final Template handlerTemplate; private final Template streamHandlerTemplate; private static String CUSTOM_INPUT_TYPE_COMBO_TEXT = "Custom"; private static Object CUSTOM_INPUT_TYPE_COMBO_DATA = new Object(); /* Check box to opt-out showing README.html */ private Button showReadmeFileCheckbox; private ISWTObservableValue showReadmeFileCheckboxObservable; private Composite parentComposite; private Group group; private Composite inputComposite; public LambdaFunctionGroup(final WizardPage parentWizard, final LambdaFunctionWizardDataModel dataModel) { this.parentWizard = parentWizard; this.dataModel = dataModel; this.bindingContext = new DataBindingContext(); this.aggregateValidationStatus = new AggregateValidationStatus( bindingContext, AggregateValidationStatus.MAX_SEVERITY); this.handlerTemplate = CodeTemplateManager.getInstance().getHandlerClassTemplate(); this.streamHandlerTemplate = CodeTemplateManager.getInstance().getStreamHandlderClassTemplate(); } // This method is supposed to be called once and before creating the controls public void init(Composite composite) { parentComposite = composite; group = newGroup(composite, "Lambda Function Handler"); GridLayout groupLayout = new GridLayout(1, true); groupLayout.marginWidth = 15; group.setLayout(groupLayout); String description = "Each Lambda function must specify a handler class " + "which the service will use as the entry point to begin execution."; setItalicFont(newLink(group, UrlConstants.webLinkListener, description + " <a href=\"" + UrlConstants.LAMBDA_EXECUTION_ROLE_DOC_URL + "\">Learn more</a> about Lambda Java function handler.", 1)); inputComposite = new Composite(group, SWT.NONE); inputComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); GridLayout inputCompLayout = new GridLayout(3, true); inputCompLayout.marginHeight = 10; inputCompLayout.marginWidth = 10; inputComposite.setLayout(inputCompLayout); } public void createPackageNameControl() { // create package name control newFillingLabel(inputComposite, "Package Name:"); handlerPackageText = newText(inputComposite, "", 2); handlerPackageTextDecoration = newControlDecoration(handlerPackageText, ""); // bind package name control to data model handlerPackageTextObservable = SWTObservables .observeText(handlerPackageText, SWT.Modify); bindingContext.bindValue(handlerPackageTextObservable, PojoObservables.observeValue(dataModel, P_HANDLER_PACKAGE_NAME)); // bind validation of package name ChainValidator<String> handlerPackageValidator = new ChainValidator<String>( handlerPackageTextObservable, new ValidPackageNameValidator("Please provide a valid package name for the handler class")); bindingContext.addValidationStatusProvider(handlerPackageValidator); new DecorationChangeListener(handlerPackageTextDecoration, handlerPackageValidator.getValidationStatus()); } public void createClassNameControl() { // create Lambda function handler class name control newFillingLabel(inputComposite, "Class Name:"); handlerClassText = newText(inputComposite, "", 2); handlerClassTextDecoration = newControlDecoration(handlerClassText, ""); // bind Lambda function handler class name control to data model handlerClassTextObservable = SWTObservables .observeText(handlerClassText, SWT.Modify); bindingContext.bindValue(handlerClassTextObservable, PojoObservables.observeValue(dataModel, P_HANDLER_CLASS_NAME)); // bind validation of class name ChainValidator<String> handlerClassValidator = new ChainValidator<String>( handlerClassTextObservable, new NotEmptyValidator("Please provide a valid class name for the handler")); bindingContext.addValidationStatusProvider(handlerClassValidator); new DecorationChangeListener(handlerClassTextDecoration, handlerClassValidator.getValidationStatus()); } public void createLambdaHandlerControl() { // create handler type control newFillingLabel(inputComposite, "Handler Type"); handlerTypeCombo = createHandlerTypeCombo(inputComposite, 1); handlerTypeDescriptionLink = newLink(inputComposite, UrlConstants.webLinkListener, "", 1); setItalicFont(handlerTypeDescriptionLink); // create input type control newFillingLabel(inputComposite, "Input Type:"); predefinedHandlerInputCombo = createPredefinedHandlerInputTypeCombo(inputComposite, 1); customHandlerInputTypeText = newText(inputComposite, "", 1); customHandlerInputTypeTextDecoration = newControlDecoration(customHandlerInputTypeText, ""); // create output type control newFillingLabel(inputComposite, "Output Type:"); handlerOutputTypeText = newText(inputComposite, "", 2); handlerOutputTypeTextDecoration = newControlDecoration(handlerOutputTypeText, ""); // bind handler type control to data model handlerTypeComboObservable = SWTObservables .observeText(handlerTypeCombo); bindingContext.bindValue(handlerTypeComboObservable, PojoObservables.observeValue(dataModel, P_HANDLER_TYPE)); // bind input type control to data model customHandlerInputTypeTextObservable = SWTObservables .observeText(customHandlerInputTypeText, SWT.Modify); bindingContext.bindValue(customHandlerInputTypeTextObservable, PojoObservables.observeValue(dataModel, P_CUSTOM_HANDLER_INPUT_TYPE)); // bind output type control to data model handlerOutputTypeTextObservable = SWTObservables .observeText(handlerOutputTypeText, SWT.Modify); bindingContext.bindValue(handlerOutputTypeTextObservable, PojoObservables.observeValue(dataModel, P_HANDLER_OUTPUT_TYPE)); // bind validation of input type ChainValidator<String> customHandlerInputChainValidator = new ChainValidator<String>( customHandlerInputTypeTextObservable, enableCustomHandlerInputTypeValidation, //enabler new NotEmptyValidator("Please provide a valid input type")); bindingContext.addValidationStatusProvider(customHandlerInputChainValidator); new DecorationChangeListener(customHandlerInputTypeTextDecoration, customHandlerInputChainValidator.getValidationStatus()); // bind validation of output type ChainValidator<String> handlerOutputChainValidator = new ChainValidator<String>( handlerOutputTypeTextObservable, new NotEmptyValidator("Please provide a valid output type")); bindingContext.addValidationStatusProvider(handlerOutputChainValidator); new DecorationChangeListener(handlerOutputTypeTextDecoration, handlerOutputChainValidator.getValidationStatus()); // bind validation of SDK installed ChainValidator<Boolean> sdkInstalledValidator = new ChainValidator<Boolean>( sdkInstalledObservable, enableSdkInstalledValidation, // enabler new BooleanValidator( "The selected input type requires the AWS Java SDK dependency. " + "Please install the SDK first " + "(Window -> Preference -> AWS Toolkit -> AWS SDK for Java) " + "and then retry.")); bindingContext.addValidationStatusProvider(sdkInstalledValidator); } public void createSeparator() { Label separator = new Label(group, SWT.HORIZONTAL | SWT.SEPARATOR); separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } private Combo createHandlerTypeCombo(Composite composite, int colspan) { final Combo combo = newCombo(composite, 1); for (LambdaHandlerType handlerType : LambdaHandlerType.values()) { combo.add(handlerType.getName()); combo.setData(handlerType.getName(), handlerType); } combo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onHandlerTypeSelectionChange(); } }); return combo; } private void onHandlerTypeSelectionChange() { final String handlerType = handlerTypeCombo.getText(); final Object handlerTypeData = handlerTypeCombo.getData(handlerType); if (STREAM_REQUEST_HANDLER == handlerTypeData) { customHandlerInputTypeText.setEnabled(false); enableCustomHandlerInputTypeValidation.setValue(false); handlerOutputTypeText.setEnabled(false); predefinedHandlerInputCombo.setEnabled(false); handlerTypeDescriptionLink.setText(createHandlerTypeDescriptionLink(STREAM_REQUEST_HANDLER)); } else if (REQUEST_HANDLER == handlerTypeData) { String selectedText = predefinedHandlerInputCombo.getText(); Object selectedData = predefinedHandlerInputCombo.getData(selectedText); boolean customHandlerInputTypeTextEnabled = selectedData == CUSTOM_INPUT_TYPE_COMBO_DATA; customHandlerInputTypeText.setEnabled(customHandlerInputTypeTextEnabled); enableCustomHandlerInputTypeValidation.setValue(customHandlerInputTypeTextEnabled); handlerOutputTypeText.setEnabled(true); predefinedHandlerInputCombo.setEnabled(true); handlerTypeDescriptionLink.setText(createHandlerTypeDescriptionLink(REQUEST_HANDLER)); } else { LambdaHandlerType lambdaHandlerType = (LambdaHandlerType)handlerTypeData; MessageDialog.openInformation(parentWizard.getShell(), "Unsupported handler type combo selection.", "The handler type " + handlerType + " is not yet supported in the toolkit! For more information, see " + lambdaHandlerType.getDocUrl() + "."); handlerTypeCombo.select(0); onHandlerTypeSelectionChange(); } } /** Return the descriptive words for the specific Lambda Handler type. */ private String createHandlerTypeDescriptionLink(LambdaHandlerType handlerType) { return "<a href=\"" + handlerType.getDocUrl() + "\">Learn more</a> about handlers."; } private Combo createPredefinedHandlerInputTypeCombo(Composite composite, int colspan) { final Combo combo = newCombo(composite, 1); for (PredefinedHandlerInputType type : PredefinedHandlerInputType.values()) { combo.add(type.getDisplayName()); combo.setData(type.getDisplayName(), type); } combo.add(CUSTOM_INPUT_TYPE_COMBO_TEXT); combo.setData(CUSTOM_INPUT_TYPE_COMBO_TEXT, CUSTOM_INPUT_TYPE_COMBO_DATA); combo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onPredefinedHandlerInputTypeComboSelectionChange(); } }); return combo; } private void onPredefinedHandlerInputTypeComboSelectionChange() { String selectedText = predefinedHandlerInputCombo.getText(); Object selectedData = predefinedHandlerInputCombo.getData(selectedText); if (selectedData == CUSTOM_INPUT_TYPE_COMBO_DATA) { customHandlerInputTypeText.setEnabled(true); enableCustomHandlerInputTypeValidation.setValue(true); dataModel.setPredefinedHandlerInputType(null); enableSdkInstalledValidation.setValue(false); } else if (selectedData instanceof PredefinedHandlerInputType) { customHandlerInputTypeText.setEnabled(false); enableCustomHandlerInputTypeValidation.setValue(false); PredefinedHandlerInputType type = (PredefinedHandlerInputType)selectedData; dataModel.setPredefinedHandlerInputType(type); enableSdkInstalledValidation.setValue(type.requireSdkDependency()); } else { LambdaPlugin.getDefault().warn("Unknown combo selection " + selectedText, null); } } public void createHandlerSourcePreview() { newFillingLabel(group, "Preview:", 1); sourcePreviewDocument = new Document(""); IPreferenceStore javaPluginPrefStore = JavaPlugin.getDefault() .getCombinedPreferenceStore(); sourcePreview = new JavaSourceViewer(group, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER, javaPluginPrefStore); sourcePreview.setEditable(false); sourcePreview.getTextWidget().setFont(JFaceResources.getFont(PreferenceConstants.EDITOR_TEXT_FONT)); // Setting up Java Syntax Highlight JavaTextTools tools= JavaPlugin.getDefault().getJavaTextTools(); tools.setupJavaDocumentPartitioner(sourcePreviewDocument, IJavaPartitions.JAVA_PARTITIONING); SimpleJavaSourceViewerConfiguration sourceConfig = new SimpleJavaSourceViewerConfiguration( tools.getColorManager(), javaPluginPrefStore, null, IJavaPartitions.JAVA_PARTITIONING, true); sourcePreview.configure(sourceConfig); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); gridData.horizontalSpan = 1; gridData.heightHint = 50; sourcePreview.getTextWidget().setLayoutData(gridData); sourcePreview.setDocument(sourcePreviewDocument); linkPreviewWithHandlerConfigInput(); } private void linkPreviewWithHandlerConfigInput() { dataModel.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { Template template = dataModel.isUseStreamHandler() ? streamHandlerTemplate : handlerTemplate; Object freeMarkerDataModel = dataModel.isUseStreamHandler() ? dataModel.collectStreamHandlerTemplateData() : dataModel.collectHandlerTemplateData(); StringWriter sw = new StringWriter(); try { template.process(freeMarkerDataModel, sw); sw.flush(); } catch (Exception e) { LambdaPlugin.getDefault().reportException( "Failed to generate handler source preview", e); } try { sw.close(); } catch (IOException ignored) { } String source = sw.toString(); if (sourcePreviewDocument != null) { sourcePreviewDocument.set(source); } if (sourcePreview != null) { sourcePreview.getTextWidget().setRedraw(true); } } }); } public void createShowReadmeFileCheckBox() { showReadmeFileCheckbox = newCheckbox(parentComposite, "Show README guide after creating the project", 1); showReadmeFileCheckboxObservable = SWTObservables.observeSelection(showReadmeFileCheckbox); bindingContext.bindValue(showReadmeFileCheckboxObservable, PojoObservables.observeValue(dataModel, P_SHOW_README_FILE)); } public void initializeValidators(IChangeListener listener) { aggregateValidationStatus.addChangeListener(listener); } public void runHandlerValidators() { Iterator<?> iterator = bindingContext.getBindings().iterator(); while (iterator.hasNext()) { Binding binding = (Binding)iterator.next(); binding.updateTargetToModel(); } } public void initializeDefaults() { if (handlerPackageTextObservable != null) { handlerPackageTextObservable.setValue(""); } if (handlerClassTextObservable != null) { handlerClassTextObservable.setValue("LambdaFunctionHandler"); } if (customHandlerInputTypeTextObservable != null) { customHandlerInputTypeTextObservable.setValue("Object"); } if (handlerOutputTypeTextObservable != null) { handlerOutputTypeTextObservable.setValue("Object"); } if (handlerTypeCombo != null) { handlerTypeCombo.select(0); onHandlerTypeSelectionChange(); } if (predefinedHandlerInputCombo != null) { predefinedHandlerInputCombo.select(0); onPredefinedHandlerInputTypeComboSelectionChange(); } if (sdkInstalledObservable != null) sdkInstalledObservable.setValue(checkSdkInstalled()); if (showReadmeFileCheckboxObservable != null) { showReadmeFileCheckboxObservable.setValue( LambdaPlugin.getDefault().getPreferenceStore() .getBoolean(LambdaPlugin.PREF_K_SHOW_README_AFTER_CREATE_NEW_PROJECT)); } } private boolean checkSdkInstalled() { return JavaSdkManager.getInstance().getDefaultSdkInstall() != null; } public IStatus getHandlerInfoValidationStatus() { if (aggregateValidationStatus == null) { return null; } Object value = aggregateValidationStatus.getValue(); if (! (value instanceof IStatus)) return null; return (IStatus)value; } private Font italicFont; private void setItalicFont(Control control) { FontData[] fontData = control.getFont() .getFontData(); for (FontData fd : fontData) { fd.setStyle(SWT.ITALIC); } italicFont = new Font(Display.getDefault(), fontData); control.setFont(italicFont); } public void dispose() { if (italicFont != null) { italicFont.dispose(); } } }