From 676b7282f41a0953fd1a756d9238a3e6380fd338 Mon Sep 17 00:00:00 2001 From: zhangzhx Date: Mon, 18 Dec 2017 19:52:24 +0000 Subject: [PATCH] AWS Toolkit for Eclipse: v201712181839 Release. --- CHANGELOG.json | 9 + .../widgets/GitCredentialsComposite.java | 11 +- .../META-INF/MANIFEST.MF | 11 +- .../etc/regions.xml | 60 ++ .../icons/flags/canada.png | Bin 0 -> 407 bytes .../icons/flags/uk.png | Bin 0 -> 3323 bytes .../icons/logo_aws.png | Bin 11240 -> 9202 bytes .../eclipse/core/AWSClientFactory.java | 6 +- .../eclipse/core/AwsToolkitCore.java | 7 +- .../eclipse/core/ansi/AnsiCommands.java | 58 ++ .../core/ansi/AnsiConsoleAttributes.java | 170 ++++++ .../core/ansi/AnsiConsoleColorPalette.java | 139 +++++ .../core/ansi/AnsiConsolePageParticipant.java | 43 ++ .../core/ansi/AnsiConsolePreferenceUtils.java | 62 ++ .../core/ansi/AnsiConsoleStyleListener.java | 214 +++++++ .../PlatformEnvironmentDataCollector.java | 2 +- .../mobileanalytics/AwsToolkitMetricType.java | 3 + .../eclipse/core/model/RegionDataModel.java | 5 + .../core/plugin/AbstractAwsPlugin.java | 4 + .../eclipse/core/regions/RegionUtils.java | 182 +++--- .../core/ui/AccountSelectionComposite.java | 5 +- .../eclipse/core/ui/ImportFileComposite.java | 226 ++++++- .../core/ui/KeyValueSetEditingComposite.java | 10 +- .../core/ui/MavenConfigurationComposite.java | 30 +- .../eclipse/core/ui/ProjectNameComposite.java | 7 +- .../eclipse/core/ui/RegionComposite.java | 6 +- .../core/ui/SelectOrInputComposite.java | 6 +- .../core/ui/wizards/WizardWidgetFactory.java | 1 + .../util/AbstractApplicationLauncher.java | 69 +++ .../amazonaws/eclipse/core/util/CliUtil.java | 115 ++++ .../eclipse/core/util/MavenBuildLauncher.java | 137 +++++ .../eclipse/core/util/OsPlatformUtils.java | 36 ++ .../eclipse/core/util/PluginUtils.java | 78 +++ .../core/util/RemoteDebugLauncher.java | 73 +++ .../eclipse/core/util/WorkbenchUtils.java | 5 +- .../core/validator/FilePathValidator.java | 10 +- .../core/validator/IntegerRangeValidator.java | 51 ++ .../validator/WorkspacePathValidator.java | 62 ++ .../core/widget/ComboViewerComplex.java | 18 +- .../eclipse/core/widget/TextComplex.java | 101 ++-- .../amazonaws/eclipse/explorer/AwsAction.java | 23 +- .../StartTestToolConfigurationWizardPage.java | 2 + .../amazonaws/eclipse/ec2/PlatformUtils.java | 61 +- .../ExternalToolsPreferencePage.java | 60 +- .../ui/databinding/MinMaxLengthValidator.java | 4 + .../META-INF/MANIFEST.MF | 12 +- .../icons/sam-local.png | Bin 0 -> 1589 bytes .../com.amazonaws.eclipse.lambda/plugin.xml | 249 ++++++-- .../eclipse/lambda/LambdaPlugin.java | 5 +- .../dialog/SamLocalGenerateEventDialog.java | 281 +++++++++ .../invoke/handler/InvokeFunctionHandler.java | 26 +- .../SamLocalConsoleColorProvider.java | 33 ++ .../launching/SamLocalConsoleLineTracker.java | 92 +++ .../lambda/launching/SamLocalConstants.java | 54 ++ .../lambda/launching/SamLocalDelegate.java | 300 ++++++++++ .../lambda/launching/SamLocalExecution.java | 184 ++++++ .../lambda/launching/SamLocalPathFinder.java | 93 +++ .../eclipse/lambda/launching/SamLocalTab.java | 554 ++++++++++++++++++ .../lambda/launching/SamLocalTabGroup.java | 31 + .../preferences/PreferenceInitializer.java | 42 ++ .../preferences/SamLocalPreferencePage.java | 132 +++++ .../model/NewServerlessProjectDataModel.java | 2 +- .../wizard/model/RunSamLocalDataModel.java | 242 ++++++++ .../NewServerlessProjectWizardPageOne.java | 5 +- .../wizard/util/FunctionProjectUtil.java | 3 +- .../wizard/util/LambdaFunctionComposite.java | 6 +- .../eclipse/lambda/serverless/Serverless.java | 51 +- .../serverless/handler/SamLocalHandler.java | 48 ++ .../serverless/model/ServerlessTemplate.java | 7 +- .../model/transform/ServerlessFunction.java | 7 +- .../model/transform/ServerlessModel.java | 11 +- .../ServerlessTemplateFilePathValidator.java | 31 +- .../wizard/editoraction/SamLocalAction.java | 41 ++ .../eclipse/lambda/ui/LambdaPluginColors.java | 26 + .../page/FunctionConfigurationPage.java | 26 +- pom.xml | 2 +- .../eclipse/core/regions/RegionUtilsTest.java | 9 +- .../.classpath | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 6 +- .../META-INF/MANIFEST.MF | 3 +- .../lambda/blueprint/BlueprintsTest.java | 2 +- .../SamLocalConsoleLineTrackerTest.java | 63 ++ .../ServerlessTemplateMapperTest.java | 226 ++++--- .../lambda/serverless/codestar.template.yml | 40 ++ .../serverless/serverless-template.yaml | 37 ++ .../META-INF/MANIFEST.MF | 318 +++++----- .../com.amazonaws.eclipse.javasdk/pom.xml | 6 +- thirdparty/pom.xml | 7 +- 88 files changed, 4852 insertions(+), 645 deletions(-) create mode 100644 bundles/com.amazonaws.eclipse.core/icons/flags/canada.png create mode 100644 bundles/com.amazonaws.eclipse.core/icons/flags/uk.png create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiCommands.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleAttributes.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleColorPalette.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePageParticipant.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePreferenceUtils.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleStyleListener.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/AbstractApplicationLauncher.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/CliUtil.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/MavenBuildLauncher.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/OsPlatformUtils.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/PluginUtils.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/RemoteDebugLauncher.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/IntegerRangeValidator.java create mode 100644 bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/WorkspacePathValidator.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/icons/sam-local.png create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/dialog/SamLocalGenerateEventDialog.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleColorProvider.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTracker.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConstants.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalDelegate.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalExecution.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalPathFinder.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTab.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTabGroup.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/PreferenceInitializer.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/SamLocalPreferencePage.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/RunSamLocalDataModel.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/handler/SamLocalHandler.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/wizard/editoraction/SamLocalAction.java create mode 100644 bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/ui/LambdaPluginColors.java create mode 100644 tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTrackerTest.java create mode 100644 tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/codestar.template.yml create mode 100644 tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/serverless-template.yaml diff --git a/CHANGELOG.json b/CHANGELOG.json index 43ebeca2..beaab57d 100644 --- a/CHANGELOG.json +++ b/CHANGELOG.json @@ -1,5 +1,14 @@ { "current": [ + "**Support AWS SAM Local to locally debug Lambda functions and AWS Gateway**", + "", + "* Debug Lambda Function", + "![debug-lambda-function](https://s3.amazonaws.com/aws-eclipse-toolkit/eclipse/release/debug-lambda-function.gif)", + "", + "* Debug API Gateway", + "![debug-api-gateway](https://s3.amazonaws.com/aws-eclipse-toolkit/eclipse/release/debug-api-gateway.gif)" + ], + "v201710260046": [ "* Update Java SDK samples." ], "v201710231659": [ diff --git a/bundles/com.amazonaws.eclipse.codecommit/src/com/amazonaws/eclipse/codecommit/widgets/GitCredentialsComposite.java b/bundles/com.amazonaws.eclipse.codecommit/src/com/amazonaws/eclipse/codecommit/widgets/GitCredentialsComposite.java index 0e7b6ac1..ca75dd39 100644 --- a/bundles/com.amazonaws.eclipse.codecommit/src/com/amazonaws/eclipse/codecommit/widgets/GitCredentialsComposite.java +++ b/bundles/com.amazonaws.eclipse.codecommit/src/com/amazonaws/eclipse/codecommit/widgets/GitCredentialsComposite.java @@ -30,6 +30,7 @@ import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.beans.PojoObservables; +import org.eclipse.core.databinding.beans.PojoProperties; import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; @@ -117,19 +118,13 @@ private void createUsernamePasswordSection() { + "Toolkit for Eclipse to create a new set of Git credentials under the current selected account. see " + "CreateServiceSpecificCredential for more information.", GIT_CREDENTIALS_DOC, CREATE_SERVICE_SPECIFIC_CREDENTIALS_DOC), 2); - usernameComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(dataBindingContext) - .pojoObservableValue(PojoObservables.observeValue(dataModel, P_USERNAME)) + usernameComplex = TextComplex.builder(this, dataBindingContext, PojoObservables.observeValue(dataModel, P_USERNAME)) .addValidator(usernameValidator == null ? new NotEmptyValidator("User name must be provided!") : usernameValidator) .labelValue("User name:") .defaultValue(dataModel.getUsername()) .build(); - passwordComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(dataBindingContext) - .pojoObservableValue(PojoObservables.observeValue(dataModel, P_PASSWORD)) + passwordComplex = TextComplex.builder(this, dataBindingContext, PojoObservables.observeValue(dataModel, P_PASSWORD)) .addValidator(passwordValidator == null ? new NotEmptyValidator("Password must be provided!") : passwordValidator) .labelValue("Password: ") .defaultValue(dataModel.getPassword()) diff --git a/bundles/com.amazonaws.eclipse.core/META-INF/MANIFEST.MF b/bundles/com.amazonaws.eclipse.core/META-INF/MANIFEST.MF index 8bf3b8cb..65af1ddc 100644 --- a/bundles/com.amazonaws.eclipse.core/META-INF/MANIFEST.MF +++ b/bundles/com.amazonaws.eclipse.core/META-INF/MANIFEST.MF @@ -16,6 +16,7 @@ Export-Package: com.amazonaws.eclipse.core, com.amazonaws.eclipse.core.accounts, com.amazonaws.eclipse.core.accounts.preferences, com.amazonaws.eclipse.core.accounts.profiles, + com.amazonaws.eclipse.core.ansi, com.amazonaws.eclipse.core.diagnostic.utils, com.amazonaws.eclipse.core.egit, com.amazonaws.eclipse.core.egit.jobs, @@ -60,6 +61,7 @@ Require-Bundle: org.eclipse.swt, org.eclipse.m2e.archetype.common;bundle-version="1.5.1", org.eclipse.m2e.maven.runtime;bundle-version="1.5.1", org.eclipse.ui.ide, + org.eclipse.ui.console, org.eclipse.egit;bundle-version="3.4.2", org.eclipse.egit.core;bundle-version="3.4.2", org.eclipse.egit.doc;bundle-version="3.4.2", @@ -68,7 +70,12 @@ Require-Bundle: org.eclipse.swt, org.eclipse.core.variables;bundle-version="3.2.800", org.eclipse.equinox.security;bundle-version="1.2.0", org.eclipse.core.databinding.property, - com.amazonaws.eclipse.javasdk;bundle-version="1.11.130" + com.amazonaws.eclipse.javasdk;bundle-version="1.11.130", + org.eclipse.m2e.launching Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy -Import-Package: org.eclipse.jdt.launching +Import-Package: org.eclipse.debug.core, + org.eclipse.debug.core.model, + org.eclipse.debug.ui, + org.eclipse.jdt.annotation, + org.eclipse.jdt.launching diff --git a/bundles/com.amazonaws.eclipse.core/etc/regions.xml b/bundles/com.amazonaws.eclipse.core/etc/regions.xml index b3b2e6ff..96022616 100644 --- a/bundles/com.amazonaws.eclipse.core/etc/regions.xml +++ b/bundles/com.amazonaws.eclipse.core/etc/regions.xml @@ -11,6 +11,7 @@ https://s3-us-gov-west-1.amazonaws.com/ https://iam.us-gov.amazonaws.com/ https://ec2.us-gov-west-1.amazonaws.com/ + https://elasticbeanstalk.us-gov-west-1.amazonaws.com/ https://sns.us-gov-west-1.amazonaws.com/ https://sqs.us-gov-west-1.amazonaws.com/ https://rds.us-gov-west-1.amazonaws.com/ @@ -18,8 +19,10 @@ https://autoscaling.us-gov-west-1.amazonaws.com/ https://monitoring.us-gov-west-1.amazonaws.com/ https://cloudformation.us-gov-west-1.amazonaws.com/ + https://lambda.us-gov-west-1.amazonaws.com/ https://dynamodb.us-gov-west-1.amazonaws.com/ https://streams.dynamodb.us-gov-west-1.amazonaws.com/ + https://codedeploy.us-gov-west-1.amazonaws.com/ https://logs.us-gov-west-1.amazonaws.com/ https://events.us-gov-west-1.amazonaws.com/ https://kms.us-gov-west-1.amazonaws.com @@ -39,6 +42,8 @@ https://queue.amazonaws.com/ https://sdb.amazonaws.com/ https://ec2.us-east-1.amazonaws.com/ + https://ecr.us-east-1.amazonaws.com/ + https://ecs.us-east-1.amazonaws.com/ https://elasticbeanstalk.us-east-1.amazonaws.com/ https://git.elasticbeanstalk.us-east-1.amazonaws.com/ https://rds.us-east-1.amazonaws.com/ @@ -72,6 +77,8 @@ https://sns.us-east-2.amazonaws.com/ https://sqs.us-east-2.amazonaws.com/ https://ec2.us-east-2.amazonaws.com/ + https://ecr.us-east-2.amazonaws.com/ + https://ecs.us-east-2.amazonaws.com/ https://rds.us-east-2.amazonaws.com/ https://cloudformation.us-east-2.amazonaws.com/ https://elasticloadbalancing.us-east-2.amazonaws.com/ @@ -88,6 +95,7 @@ https://codestar.us-east-2.amazonaws.com https://events.us-east-2.amazonaws.com/ https://kms.us-east-2.amazonaws.com + https://opsworks.us-east-2.amazonaws.com/ @@ -104,6 +112,8 @@ https://sqs.us-west-2.amazonaws.com/ https://sdb.us-west-2.amazonaws.com/ https://ec2.us-west-2.amazonaws.com/ + https://ecr.us-west-2.amazonaws.com/ + https://ecs.us-west-2.amazonaws.com/ https://rds.us-west-2.amazonaws.com/ https://cloudformation.us-west-2.amazonaws.com/ https://elasticloadbalancing.us-west-2.amazonaws.com/ @@ -121,6 +131,7 @@ https://codestar.us-west-2.amazonaws.com https://events.us-west-2.amazonaws.com/ https://kms.us-west-2.amazonaws.com + https://opsworks.us-west-2.amazonaws.com/ @@ -137,6 +148,8 @@ https://sqs.us-west-1.amazonaws.com/ https://sdb.us-west-1.amazonaws.com/ https://ec2.us-west-1.amazonaws.com/ + https://ecr.us-west-1.amazonaws.com/ + https://ecs.us-west-1.amazonaws.com/ https://rds.us-west-1.amazonaws.com/ https://cloudformation.us-west-1.amazonaws.com/ https://elasticloadbalancing.us-west-1.amazonaws.com/ @@ -149,8 +162,11 @@ https://kinesis.us-west-1.amazonaws.com/ https://lambda.us-west-1.amazonaws.com/ https://logs.us-west-1.amazonaws.com/ + https://codecommit.us-west-1.amazonaws.com https://events.us-west-1.amazonaws.com/ https://kms.us-west-1.amazonaws.com + https://codestar.us-west-1.amazonaws.com + https://opsworks.us-west-1.amazonaws.com/ @@ -166,6 +182,8 @@ https://sns.ca-central-1.amazonaws.com/ https://sqs.ca-central-1.amazonaws.com/ https://ec2.ca-central-1.amazonaws.com/ + https://ecr.ca-central-1.amazonaws.com/ + https://ecs.ca-central-1.amazonaws.com/ https://rds.ca-central-1.amazonaws.com/ https://cloudformation.ca-central-1.amazonaws.com/ https://elasticloadbalancing.ca-central-1.amazonaws.com/ @@ -176,8 +194,11 @@ https://elasticbeanstalk.ca-central-1.amazonaws.com/ https://codedeploy.ca-central-1.amazonaws.com/ https://kinesis.ca-central-1.amazonaws.com/ + https://lambda.ca-central-1.amazonaws.com/ https://logs.ca-central-1.amazonaws.com/ + https://codecommit.ca-central-1.amazonaws.com https://kms.ca-central-1.amazonaws.com + https://opsworks.ca-central-1.amazonaws.com/ @@ -194,6 +215,8 @@ https://sqs.eu-west-1.amazonaws.com/ https://sdb.eu-west-1.amazonaws.com/ https://ec2.eu-west-1.amazonaws.com/ + https://ecr.eu-west-1.amazonaws.com/ + https://ecs.eu-west-1.amazonaws.com/ https://elasticbeanstalk.eu-west-1.amazonaws.com/ https://git.elasticbeanstalk.eu-west-1.amazonaws.com/ https://rds.eu-west-1.amazonaws.com/ @@ -211,6 +234,7 @@ https://codestar.eu-west-1.amazonaws.com https://events.eu-west-1.amazonaws.com/ https://kms.eu-west-1.amazonaws.com + https://opsworks.eu-west-1.amazonaws.com/ @@ -226,6 +250,8 @@ https://sns.eu-west-2.amazonaws.com/ https://sqs.eu-west-2.amazonaws.com/ https://ec2.eu-west-2.amazonaws.com/ + https://ecr.eu-west-2.amazonaws.com/ + https://ecs.eu-west-2.amazonaws.com/ https://elasticbeanstalk.eu-west-2.amazonaws.com/ https://git.elasticbeanstalk.eu-west-2.amazonaws.com/ https://rds.eu-west-2.amazonaws.com/ @@ -236,9 +262,13 @@ https://codedeploy.eu-west-2.amazonaws.com/ https://dynamodb.eu-west-2.amazonaws.com/ https://streams.dynamodb.eu-west-2.amazonaws.com/ + https://lambda.eu-west-2.amazonaws.com/ https://kinesis.eu-west-2.amazonaws.com/ https://logs.eu-west-2.amazonaws.com/ + https://codecommit.eu-west-2.amazonaws.com https://kms.eu-west-2.amazonaws.com + https://codestar.eu-west-2.amazonaws.com + https://opsworks.eu-west-2.amazonaws.com/ @@ -254,6 +284,8 @@ https://sns.eu-central-1.amazonaws.com/ https://sqs.eu-central-1.amazonaws.com/ https://ec2.eu-central-1.amazonaws.com/ + https://ecr.eu-central-1.amazonaws.com/ + https://ecs.eu-central-1.amazonaws.com/ https://elasticbeanstalk.eu-central-1.amazonaws.com/ https://git.elasticbeanstalk.eu-central-1.amazonaws.com/ https://rds.eu-central-1.amazonaws.com/ @@ -265,9 +297,12 @@ https://streams.dynamodb.eu-central-1.amazonaws.com/ https://lambda.eu-central-1.amazonaws.com/ https://kinesis.eu-central-1.amazonaws.com/ + https://codecommit.eu-central-1.amazonaws.com https://logs.eu-central-1.amazonaws.com/ + https://codestar.eu-central-1.amazonaws.com https://events.eu-central-1.amazonaws.com/ https://kms.eu-central-1.amazonaws.com + https://opsworks.eu-central-1.amazonaws.com/ @@ -283,6 +318,8 @@ https://sns.ap-south-1.amazonaws.com/ https://sqs.ap-south-1.amazonaws.com/ https://ec2.ap-south-1.amazonaws.com/ + https://ecr.ap-south-1.amazonaws.com/ + https://ecs.ap-south-1.amazonaws.com/ https://rds.ap-south-1.amazonaws.com/ https://cloudformation.ap-south-1.amazonaws.com/ https://elasticbeanstalk.ap-south-1.amazonaws.com/ @@ -294,8 +331,10 @@ https://lambda.ap-south-1.amazonaws.com/ https://kinesis.ap-south-1.amazonaws.com/ https://logs.ap-south-1.amazonaws.com/ + https://codecommit.ap-south-1.amazonaws.com https://events.ap-south-1.amazonaws.com/ https://kms.ap-south-1.amazonaws.com + https://opsworks.ap-south-1.amazonaws.com/ @@ -312,6 +351,8 @@ https://sqs.ap-southeast-1.amazonaws.com/ https://sdb.ap-southeast-1.amazonaws.com/ https://ec2.ap-southeast-1.amazonaws.com/ + https://ecr.ap-southeast-1.amazonaws.com/ + https://ecs.ap-southeast-1.amazonaws.com/ https://rds.ap-southeast-1.amazonaws.com/ https://cloudformation.ap-southeast-1.amazonaws.com/ https://elasticbeanstalk.ap-southeast-1.amazonaws.com/ @@ -323,9 +364,12 @@ https://streams.dynamodb.ap-southeast-1.amazonaws.com/ https://kinesis.ap-southeast-1.amazonaws.com/ https://logs.ap-southeast-1.amazonaws.com/ + https://codecommit.ap-southeast-1.amazonaws.com https://lambda.ap-southeast-1.amazonaws.com/ + https://codestar.ap-southeast-1.amazonaws.com https://events.ap-southeast-1.amazonaws.com/ https://kms.ap-southeast-1.amazonaws.com + https://opsworks.ap-southeast-1.amazonaws.com/ @@ -342,6 +386,8 @@ http://sqs.ap-northeast-1.amazonaws.com/ https://sdb.ap-northeast-1.amazonaws.com/ https://ec2.ap-northeast-1.amazonaws.com/ + https://ecr.ap-northeast-1.amazonaws.com/ + https://ecs.ap-northeast-1.amazonaws.com/ https://rds.ap-northeast-1.amazonaws.com/ https://cloudformation.ap-northeast-1.amazonaws.com/ https://elasticbeanstalk.ap-northeast-1.amazonaws.com/ @@ -354,9 +400,11 @@ https://streams.dynamodb.ap-northeast-1.amazonaws.com/ https://kinesis.ap-northeast-1.amazonaws.com/ https://logs.ap-northeast-1.amazonaws.com/ + https://codecommit.ap-northeast-1.amazonaws.com https://lambda.ap-northeast-1.amazonaws.com/ https://events.ap-northeast-1.amazonaws.com/ https://kms.ap-northeast-1.amazonaws.com + https://opsworks.ap-northeast-1.amazonaws.com/ @@ -384,9 +432,11 @@ https://streams.dynamodb.ap-northeast-2.amazonaws.com/ https://kinesis.ap-northeast-2.amazonaws.com/ https://logs.ap-northeast-2.amazonaws.com/ + https://codecommit.ap-northeast-2.amazonaws.com https://lambda.ap-northeast-2.amazonaws.com/ https://events.ap-northeast-2.amazonaws.com/ https://kms.ap-northeast-2.amazonaws.com + https://opsworks.ap-northeast-2.amazonaws.com/ @@ -403,6 +453,8 @@ https://sqs.ap-southeast-2.amazonaws.com/ https://sdb.ap-southeast-2.amazonaws.com/ https://ec2.ap-southeast-2.amazonaws.com/ + https://ecr.ap-southeast-2.amazonaws.com/ + https://ecs.ap-southeast-2.amazonaws.com/ https://rds.ap-southeast-2.amazonaws.com/ https://cloudformation.ap-southeast-2.amazonaws.com/ https://elasticbeanstalk.ap-southeast-2.amazonaws.com/ @@ -415,9 +467,12 @@ https://streams.dynamodb.ap-southeast-2.amazonaws.com/ https://kinesis.ap-southeast-2.amazonaws.com/ https://logs.ap-southeast-2.amazonaws.com/ + https://codecommit.ap-southeast-2.amazonaws.com https://lambda.ap-southeast-2.amazonaws.com/ + https://codestar.ap-southeast-2.amazonaws.com https://events.ap-southeast-2.amazonaws.com/ https://kms.ap-southeast-2.amazonaws.com + https://opsworks.ap-southeast-2.amazonaws.com/ @@ -444,8 +499,11 @@ https://elasticbeanstalk.sa-east-1.amazonaws.com/ https://git.elasticbeanstalk.sa-east-1.amazonaws.com/ https://logs.sa-east-1.amazonaws.com/ + https://codecommit.sa-east-1.amazonaws.com + https://lambda.sa-east-1.amazonaws.com/ https://events.sa-east-1.amazonaws.com/ https://kms.sa-east-1.amazonaws.com + https://opsworks.sa-east-1.amazonaws.com/ @@ -459,6 +517,8 @@ https://s3.cn-north-1.amazonaws.com.cn/ https://iam.cn-north-1.amazonaws.com.cn/ https://ec2.cn-north-1.amazonaws.com.cn/ + https://ecr.cn-north-1.amazonaws.com.cn/ + https://ecs.cn-north-1.amazonaws.com.cn/ https://sns.cn-north-1.amazonaws.com.cn/ https://sqs.cn-north-1.amazonaws.com.cn/ https://rds.cn-north-1.amazonaws.com.cn/ diff --git a/bundles/com.amazonaws.eclipse.core/icons/flags/canada.png b/bundles/com.amazonaws.eclipse.core/icons/flags/canada.png new file mode 100644 index 0000000000000000000000000000000000000000..7988ee52554a43d6d8a523da7d8551c166fc2765 GIT binary patch literal 407 zcmV;I0cie-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U7009R{L_t(2QPQpMC#(4}uBL*+T6E_Rd ziU-0$pMZmj2VVdPB;Fh2i|I`tq0-&{vxE>Jsb#zSIo&{GBF6G1-)3eq^Gjy3CA-qZ zq-<#0Cni*yLMgS|Qo%lL=lvfQ10_a*dvLDJswN;Mwi1gK#C);INCaWEtGu7n?4N)n zoFW=NA?jO5{7}~l1HK#&%hz|XqZasK6U&!3#1pPp_!poE;CJre)jQzTOSs1uaL>B% zu6x8T0FWZZ=lD6VZ5<9Q$W#_vA-j0+@CdhY3*NYae?QW-y^bi?0!a`d=7Em2_5oxP zvpLq$Y?G080UUaGmXIC#NIwC;L;$DO&ih%}z&B{1&J&sEy&C`k002ovPDHLkV1o6m BqLTms literal 0 HcmV?d00001 diff --git a/bundles/com.amazonaws.eclipse.core/icons/flags/uk.png b/bundles/com.amazonaws.eclipse.core/icons/flags/uk.png new file mode 100644 index 0000000000000000000000000000000000000000..c00506679ee8f90c5465fce2d47c2bc1ef8c5bcd GIT binary patch literal 3323 zcmV004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( 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 delta 10921 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 z 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 LNkvXXu0mjfwA)^n diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AWSClientFactory.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AWSClientFactory.java index 23c1f28a..93d71b3d 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AWSClientFactory.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AWSClientFactory.java @@ -570,11 +570,7 @@ private T createClient(String endpoint, Class // Low layer method for building a service client by using the client builder. @SuppressWarnings("unchecked") private T createClientByRegion(AwsSyncClientBuilder builder, String region, String endpoint) { - if (region.equals("local")) { - builder.withEndpointConfiguration(new EndpointConfiguration(endpoint, region)); - } else { - builder.withRegion(region); - } + builder.withEndpointConfiguration(new EndpointConfiguration(endpoint, region)); Object client = builder .withCredentials(new AWSStaticCredentialsProvider(getAwsCredentials())) .withClientConfiguration(createClientConfiguration(endpoint)) diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AwsToolkitCore.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AwsToolkitCore.java index e2fe35b3..eb9d0499 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AwsToolkitCore.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/AwsToolkitCore.java @@ -523,8 +523,9 @@ private static void registerCustomErrorSupport() { Policy.setErrorSupportProvider(awsProvider); } - // TODO: any better way to check debug mode? - public static final boolean DEBUG_MODE = false; + public boolean isDebugMode() { + return getBundleVersion().contains("qualifier"); + } private ToolkitAnalyticsManager initializeToolkitAnalyticsManager() { @@ -535,7 +536,7 @@ private ToolkitAnalyticsManager initializeToolkitAnalyticsManager() { if (enabled) { try { - if (DEBUG_MODE) { + if (isDebugMode()) { toReturn = new ToolkitAnalyticsManagerImpl( AWSCognitoCredentialsProvider.TEST_PROVIDER, ClientContextConfig.TEST_CONFIG); diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiCommands.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiCommands.java new file mode 100644 index 00000000..f4d0833c --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiCommands.java @@ -0,0 +1,58 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +//From Wikipedia, http://en.wikipedia.org/wiki/ANSI_escape_code +public class AnsiCommands { + public static final int COMMAND_ATTR_RESET = 0; // Reset / Normal (all attributes off) + public static final int COMMAND_ATTR_INTENSITY_BRIGHT = 1; // Bright (increased intensity) or Bold + public static final int COMMAND_ATTR_INTENSITY_FAINT = 2; // Faint (decreased intensity) (not widely supported) + public static final int COMMAND_ATTR_ITALIC = 3; // Italic: on not widely supported. Sometimes treated as inverse. + public static final int COMMAND_ATTR_UNDERLINE = 4; // Underline: Single + public static final int COMMAND_ATTR_BLINK_SLOW = 5; // Blink: Slow (less than 150 per minute) + public static final int COMMAND_ATTR_BLINK_FAST = 6; // Blink: Rapid (MS-DOS ANSI.SYS; 150 per minute or more; not widely supported) + public static final int COMMAND_ATTR_NEGATIVE_ON = 7; // Image: Negative (inverse or reverse; swap foreground and background) + public static final int COMMAND_ATTR_CONCEAL_ON = 8; // Conceal (not widely supported) + public static final int COMMAND_ATTR_CROSSOUT_ON = 9; // Crossed-out (Characters legible, but marked for deletion. Not widely supported.) + public static final int COMMAND_ATTR_UNDERLINE_DOUBLE = 21; // Bright/Bold: off or Underline: Double (bold off not widely supported, double underline hardly ever) + public static final int COMMAND_ATTR_INTENSITY_NORMAL = 22; // Normal color or intensity (neither bright, bold nor faint) + public static final int COMMAND_ATTR_ITALIC_OFF = 23; // Not italic, not Fraktur + public static final int COMMAND_ATTR_UNDERLINE_OFF = 24; // Underline: None (not singly or doubly underlined) + public static final int COMMAND_ATTR_BLINK_OFF = 25; // Blink: off + public static final int COMMAND_ATTR_NEGATIVE_OFF = 27; // Image: Positive + public static final int COMMAND_ATTR_CONCEAL_OFF = 28; // Reveal (conceal off) + public static final int COMMAND_ATTR_CROSSOUT_OFF = 29; // Not crossed out + + // Extended colors. Next arguments are 5; or 2;;; + public static final int COMMAND_HICOLOR_FOREGROUND = 38; // Set text color + public static final int COMMAND_HICOLOR_BACKGROUND = 48; // Set background color + + public static final int COMMAND_COLOR_FOREGROUND_RESET = 39; // Default text color + public static final int COMMAND_COLOR_BACKGROUND_RESET = 49; // Default background color + + public static final int COMMAND_COLOR_FOREGROUND_FIRST = 30; // First text color + public static final int COMMAND_COLOR_FOREGROUND_LAST = 37; // Last text color + public static final int COMMAND_COLOR_BACKGROUND_FIRST = 40; // First background text color + public static final int COMMAND_COLOR_BACKGROUND_LAST = 47; // Last background text color + + public static final int COMMAND_ATTR_FRAMED_ON = 51; // Framed + public static final int COMMAND_ATTR_FRAMED_OFF = 54; // Not framed or encircled + + public static final int COMMAND_HICOLOR_FOREGROUND_FIRST = 90; // First text color + public static final int COMMAND_HICOLOR_FOREGROUND_LAST = 97; // Last text color + public static final int COMMAND_HICOLOR_BACKGROUND_FIRST = 100; // First background text color + public static final int COMMAND_HICOLOR_BACKGROUND_LAST = 107; // Last background text color + + public static final int COMMAND_COLOR_INTENSITY_DELTA = 8; // Last background text color +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleAttributes.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleAttributes.java new file mode 100644 index 00000000..a7bed711 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleAttributes.java @@ -0,0 +1,170 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; + +import com.amazonaws.eclipse.core.util.OsPlatformUtils; + +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.*; + +public class AnsiConsoleAttributes implements Cloneable { + public final static int UNDERLINE_NONE = -1; // nothing in SWT, a bit of an abuse + + public Integer currentBgColor; + public Integer currentFgColor; + public int underline; + public boolean bold; + public boolean italic; + public boolean invert; + public boolean conceal; + public boolean strike; + public boolean framed; + + public AnsiConsoleAttributes() { + reset(); + } + + public void reset() { + currentBgColor = null; + currentFgColor = null; + underline = UNDERLINE_NONE; + bold = false; + italic = false; + invert = false; + conceal = false; + strike = false; + framed = false; + } + + @Override + public AnsiConsoleAttributes clone() { + AnsiConsoleAttributes result = new AnsiConsoleAttributes(); + result.currentBgColor = currentBgColor; + result.currentFgColor = currentFgColor; + result.underline = underline; + result.bold = bold; + result.italic = italic; + result.invert = invert; + result.conceal = conceal; + result.strike = strike; + result.framed = framed; + return result; + } + + public static Color hiliteRgbColor(Color c) { + if (c == null) + return new Color(null, new RGB(0xff, 0xff, 0xff)); + int red = c.getRed() * 2; + int green = c.getGreen() * 2; + int blue = c.getBlue() * 2; + + if (red > 0xff) red = 0xff; + if (green > 0xff) green = 0xff; + if (blue > 0xff) blue = 0xff; + + return new Color(null, new RGB(red, green, blue)); // here + } + + // This function maps from the current attributes as "described" by escape sequences to real, + // Eclipse console specific attributes (resolving color palette, default colors, etc.) + public static void updateRangeStyle(StyleRange range, AnsiConsoleAttributes attribute) { + boolean useWindowsMapping = OsPlatformUtils.isWindows(); + AnsiConsoleAttributes tempAttrib = attribute.clone(); + + boolean hilite = false; + + if (useWindowsMapping) { + if (tempAttrib.bold) { + tempAttrib.bold = false; // not supported, rendered as intense, already done that + hilite = true; + } + if (tempAttrib.italic) { + tempAttrib.italic = false; + tempAttrib.invert = true; + } + tempAttrib.underline = UNDERLINE_NONE; // not supported on Windows + tempAttrib.strike = false; // not supported on Windows + tempAttrib.framed = false; // not supported on Windows + } + + // Prepare the foreground color + if (hilite) { + if (tempAttrib.currentFgColor == null) { + range.foreground = AnsiConsolePreferenceUtils.getDebugConsoleFgColor(); + range.foreground = hiliteRgbColor(range.foreground); + } else { + if (tempAttrib.currentFgColor < COMMAND_COLOR_INTENSITY_DELTA) + range.foreground = new Color(null, AnsiConsoleColorPalette.getColor(tempAttrib.currentFgColor + COMMAND_COLOR_INTENSITY_DELTA)); + else + range.foreground = new Color(null, AnsiConsoleColorPalette.getColor(tempAttrib.currentFgColor)); + } + } else { + if (tempAttrib.currentFgColor != null) + range.foreground = new Color(null, AnsiConsoleColorPalette.getColor(tempAttrib.currentFgColor)); + } + + // Prepare the background color + if (tempAttrib.currentBgColor != null) + range.background = new Color(null, AnsiConsoleColorPalette.getColor(tempAttrib.currentBgColor)); + + // These two still mess with the foreground/background colors + // We need to solve them before we use them for strike/underline/frame colors + if (tempAttrib.invert) { + if (range.foreground == null) + range.foreground = AnsiConsolePreferenceUtils.getDebugConsoleFgColor(); + if (range.background == null) + range.background = AnsiConsolePreferenceUtils.getDebugConsoleBgColor(); + Color tmp = range.background; + range.background = range.foreground; + range.foreground = tmp; + } + + if (tempAttrib.conceal) { + if (range.background == null) + range.background = AnsiConsolePreferenceUtils.getDebugConsoleBgColor(); + range.foreground = range.background; + } + + range.font = null; + range.fontStyle = SWT.NORMAL; + // Prepare the rest of the attributes + if (tempAttrib.bold) + range.fontStyle |= SWT.BOLD; + + if (tempAttrib.italic) + range.fontStyle |= SWT.ITALIC; + + if (tempAttrib.underline != UNDERLINE_NONE) { + range.underline = true; + range.underlineColor = range.foreground; + range.underlineStyle = tempAttrib.underline; + } + else + range.underline = false; + + range.strikeout = tempAttrib.strike; + range.strikeoutColor = range.foreground; + + if (tempAttrib.framed) { + range.borderStyle = SWT.BORDER_SOLID; + range.borderColor = range.foreground; + } + else + range.borderStyle = SWT.NONE; + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleColorPalette.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleColorPalette.java new file mode 100644 index 00000000..10b7fdd7 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleColorPalette.java @@ -0,0 +1,139 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +import org.eclipse.swt.graphics.RGB; + +import com.amazonaws.eclipse.core.util.OsPlatformUtils; + +public class AnsiConsoleColorPalette { + private static final int PALETTE_SIZE = 256; + + // From Wikipedia, http://en.wikipedia.org/wiki/ANSI_escape_code + private final static RGB[] paletteXP = { + new RGB( 0, 0, 0), // black + new RGB(128, 0, 0), // red + new RGB( 0, 128, 0), // green + new RGB(128, 128, 0), // brown/yellow + new RGB( 0, 0, 128), // blue + new RGB(128, 0, 128), // magenta + new RGB( 0, 128, 128), // cyan + new RGB(192, 192, 192), // gray + new RGB(128, 128, 128), // dark gray + new RGB(255, 0, 0), // bright red + new RGB( 0, 255, 0), // bright green + new RGB(255, 255, 0), // yellow + new RGB( 0, 0, 255), // bright blue + new RGB(255, 0, 255), // bright magenta + new RGB( 0, 255, 255), // bright cyan + new RGB(255, 255, 255) // white + }; + private final static RGB[] paletteMac = { + new RGB( 0, 0, 0), // black + new RGB(194, 54, 33), // red + new RGB( 37, 188, 36), // green + new RGB(173, 173, 39), // brown/yellow + new RGB( 73, 46, 225), // blue + new RGB(211, 56, 211), // magenta + new RGB( 51, 187, 200), // cyan + new RGB(203, 204, 205), // gray + new RGB(129, 131, 131), // dark gray + new RGB(252, 57, 31), // bright red + new RGB( 49, 231, 34), // bright green + new RGB(234, 236, 35), // yellow + new RGB( 88, 51, 255), // bright blue + new RGB(249, 53, 248), // bright magenta + new RGB( 20, 240, 240), // bright cyan + new RGB(233, 235, 235) // white + }; + private final static RGB[] paletteXTerm = { + new RGB( 0, 0, 0), // black + new RGB(205, 0, 0), // red + new RGB( 0, 205, 0), // green + new RGB(205, 205, 0), // brown/yellow + new RGB( 0, 0, 238), // blue + new RGB(205, 0, 205), // magenta + new RGB( 0, 205, 205), // cyan + new RGB(229, 229, 229), // gray + new RGB(127, 127, 127), // dark gray + new RGB(255, 0, 0), // bright red + new RGB( 0, 255, 0), // bright green + new RGB(255, 255, 0), // yellow + new RGB( 92, 92, 255), // bright blue + new RGB(255, 0, 255), // bright magenta + new RGB( 0, 255, 255), // bright cyan + new RGB(255, 255, 255) // white + }; + private static RGB[] palette = getDefaultPalette(); + + public static boolean isValidIndex(int value) { + return value >= 0 && value < PALETTE_SIZE; + } + + static int TRUE_RGB_FLAG = 0x10000000; // Representing true RGB colors as 0x10RRGGBB + + public static int hackRgb(int r, int g, int b) { + if (!isValidIndex(r)) return -1; + if (!isValidIndex(g)) return -1; + if (!isValidIndex(b)) return -1; + return TRUE_RGB_FLAG | r << 16 | g << 8 | b; + } + + static int safe256(int value, int modulo) { + int result = value * PALETTE_SIZE / modulo; + return result < PALETTE_SIZE ? result : PALETTE_SIZE - 1; + } + + public static RGB getColor(Integer index) { + if (null == index) + return null; + + if (index >= TRUE_RGB_FLAG) { + int red = index >> 16 & 0xff; + int green = index >> 8 & 0xff; + int blue = index & 0xff; + return new RGB(red, green, blue); + } + + if (index >= 0 && index < palette.length) // basic, 16 color palette + return palette[index]; + + if (index >= 16 && index < 232) { // 6x6x6 color matrix + int color = index - 16; + int blue = color % 6; + color = color / 6; + int green = color % 6; + int red = color / 6; + + return new RGB(safe256(red, 6), safe256(green, 6), safe256(blue, 6)); + } + + if (index >= 232 && index < PALETTE_SIZE) { // grayscale + int gray = safe256(index - 232, 24); + return new RGB(gray, gray, gray); + } + + return null; + } + + private static RGB[] getDefaultPalette() { + if (OsPlatformUtils.isWindows()) { + return paletteXP; + } else if (OsPlatformUtils.isMac()) { + return paletteMac; + } else { + return paletteXTerm; + } + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePageParticipant.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePageParticipant.java new file mode 100644 index 00000000..66020294 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePageParticipant.java @@ -0,0 +1,43 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsolePageParticipant; +import org.eclipse.ui.part.IPageBookViewPage; + +public class AnsiConsolePageParticipant implements IConsolePageParticipant { + @Override + public Object getAdapter(Class adapter) { + return null; + } + + @Override + public void activated() {} + + @Override + public void deactivated() {} + + @Override + public void dispose() {} + + @Override + public void init(IPageBookViewPage page, IConsole console) { + if (page.getControl() instanceof StyledText) { + StyledText viewer = (StyledText) page.getControl(); + viewer.addLineStyleListener(new AnsiConsoleStyleListener()); + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePreferenceUtils.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePreferenceUtils.java new file mode 100644 index 00000000..19501c12 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsolePreferenceUtils.java @@ -0,0 +1,62 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; + +public class AnsiConsolePreferenceUtils { + private final static String DEBUG_CONSOLE_PLUGIN_ID = "org.eclipse.debug.ui"; + private final static String DEBUG_CONSOLE_FALLBACK_BKCOLOR = "0,0,0"; + private final static String DEBUG_CONSOLE_FALLBACK_FGCOLOR = "192,192,192"; + + static Color colorFromStringRgb(String strRgb) { + Color result = null; + String[] splitted = strRgb.split(","); + if (splitted != null && splitted.length == 3) { + int red = tryParseInteger(splitted[0]); + int green = tryParseInteger(splitted[1]); + int blue = tryParseInteger(splitted[2]); + result = new Color(null, new RGB(red, green, blue)); + } + return result; + } + + public static Color getDebugConsoleBgColor() { + IPreferencesService ps = Platform.getPreferencesService(); + String value = ps.getString(DEBUG_CONSOLE_PLUGIN_ID, "org.eclipse.debug.ui.consoleBackground", + DEBUG_CONSOLE_FALLBACK_BKCOLOR, null); + return colorFromStringRgb(value); + } + + public static Color getDebugConsoleFgColor() { + IPreferencesService ps = Platform.getPreferencesService(); + String value = ps.getString(DEBUG_CONSOLE_PLUGIN_ID, "org.eclipse.debug.ui.outColor", + DEBUG_CONSOLE_FALLBACK_FGCOLOR, null); + return colorFromStringRgb(value); + } + + public static int tryParseInteger(String text) { + if ("".equals(text)) + return -1; + + try { + return Integer.parseInt(text); + } catch (NumberFormatException e) { + return -1; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleStyleListener.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleStyleListener.java new file mode 100644 index 00000000..6cd99b87 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ansi/AnsiConsoleStyleListener.java @@ -0,0 +1,214 @@ +/** + * 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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.core.ansi; + +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_CONCEAL_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_CONCEAL_ON; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_CROSSOUT_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_CROSSOUT_ON; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_FRAMED_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_FRAMED_ON; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_INTENSITY_BRIGHT; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_INTENSITY_FAINT; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_INTENSITY_NORMAL; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_ITALIC; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_ITALIC_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_NEGATIVE_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_NEGATIVE_ON; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_RESET; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_UNDERLINE; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_UNDERLINE_DOUBLE; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_ATTR_UNDERLINE_OFF; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_BACKGROUND_FIRST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_BACKGROUND_LAST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_BACKGROUND_RESET; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_FOREGROUND_FIRST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_FOREGROUND_LAST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_FOREGROUND_RESET; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_COLOR_INTENSITY_DELTA; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_BACKGROUND; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_BACKGROUND_FIRST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_BACKGROUND_LAST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_FOREGROUND; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_FOREGROUND_FIRST; +import static com.amazonaws.eclipse.core.ansi.AnsiCommands.COMMAND_HICOLOR_FOREGROUND_LAST; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.LineStyleEvent; +import org.eclipse.swt.custom.LineStyleListener; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GlyphMetrics; + +public class AnsiConsoleStyleListener implements LineStyleListener { + private AnsiConsoleAttributes lastAttributes = new AnsiConsoleAttributes(); + private AnsiConsoleAttributes currentAttributes = new AnsiConsoleAttributes(); + private final static Pattern pattern = Pattern.compile("\u001b\\[[\\d;]*[A-HJKSTfimnsu]"); + private final static char ESCAPE_SGR = 'm'; + + int lastRangeEnd = 0; + + private boolean interpretCommand(List nCommands) { + boolean result = false; + + Iterator iter = nCommands.iterator(); + while (iter.hasNext()) { + int nCmd = iter.next(); + switch (nCmd) { + case COMMAND_ATTR_RESET: currentAttributes.reset(); break; + + case COMMAND_ATTR_INTENSITY_BRIGHT: currentAttributes.bold = true; break; + case COMMAND_ATTR_INTENSITY_FAINT: currentAttributes.bold = false; break; + case COMMAND_ATTR_INTENSITY_NORMAL: currentAttributes.bold = false; break; + + case COMMAND_ATTR_ITALIC: currentAttributes.italic = true; break; + case COMMAND_ATTR_ITALIC_OFF: currentAttributes.italic = false; break; + + case COMMAND_ATTR_UNDERLINE: currentAttributes.underline = SWT.UNDERLINE_SINGLE; break; + case COMMAND_ATTR_UNDERLINE_DOUBLE: currentAttributes.underline = SWT.UNDERLINE_DOUBLE; break; + case COMMAND_ATTR_UNDERLINE_OFF: currentAttributes.underline = AnsiConsoleAttributes.UNDERLINE_NONE; break; + + case COMMAND_ATTR_CROSSOUT_ON: currentAttributes.strike = true; break; + case COMMAND_ATTR_CROSSOUT_OFF: currentAttributes.strike = false; break; + + case COMMAND_ATTR_NEGATIVE_ON: currentAttributes.invert = true; break; + case COMMAND_ATTR_NEGATIVE_OFF: currentAttributes.invert = false; break; + + case COMMAND_ATTR_CONCEAL_ON: currentAttributes.conceal = true; break; + case COMMAND_ATTR_CONCEAL_OFF: currentAttributes.conceal = false; break; + + case COMMAND_ATTR_FRAMED_ON: currentAttributes.framed = true; break; + case COMMAND_ATTR_FRAMED_OFF: currentAttributes.framed = false; break; + + case COMMAND_COLOR_FOREGROUND_RESET: currentAttributes.currentFgColor = null; break; + case COMMAND_COLOR_BACKGROUND_RESET: currentAttributes.currentBgColor = null; break; + + case COMMAND_HICOLOR_FOREGROUND: + case COMMAND_HICOLOR_BACKGROUND: // {esc}[48;5;{color}m + int color = -1; + int nMustBe2or5 = iter.hasNext() ? iter.next() : -1; + if (nMustBe2or5 == 5) { // 256 colors + color = iter.hasNext() ? iter.next() : -1; + if (!AnsiConsoleColorPalette.isValidIndex(color)) + color = -1; + } else if (nMustBe2or5 == 2) { // rgb colors + int r = iter.hasNext() ? iter.next() : -1; + int g = iter.hasNext() ? iter.next() : -1; + int b = iter.hasNext() ? iter.next() : -1; + color = AnsiConsoleColorPalette.hackRgb(r, g, b); + } + if (color != -1) { + if (nCmd == COMMAND_HICOLOR_FOREGROUND) + currentAttributes.currentFgColor = color; + else + currentAttributes.currentBgColor = color; + } + break; + + case -1: break; // do nothing + + default: + if (nCmd >= COMMAND_COLOR_FOREGROUND_FIRST && nCmd <= COMMAND_COLOR_FOREGROUND_LAST) // text color + currentAttributes.currentFgColor = nCmd - COMMAND_COLOR_FOREGROUND_FIRST; + else if (nCmd >= COMMAND_COLOR_BACKGROUND_FIRST && nCmd <= COMMAND_COLOR_BACKGROUND_LAST) // background color + currentAttributes.currentBgColor = nCmd - COMMAND_COLOR_BACKGROUND_FIRST; + else if (nCmd >= COMMAND_HICOLOR_FOREGROUND_FIRST && nCmd <= COMMAND_HICOLOR_FOREGROUND_LAST) // text color + currentAttributes.currentFgColor = nCmd - COMMAND_HICOLOR_FOREGROUND_FIRST + COMMAND_COLOR_INTENSITY_DELTA; + else if (nCmd >= COMMAND_HICOLOR_BACKGROUND_FIRST && nCmd <= COMMAND_HICOLOR_BACKGROUND_LAST) // background color + currentAttributes.currentBgColor = nCmd - COMMAND_HICOLOR_BACKGROUND_FIRST + COMMAND_COLOR_INTENSITY_DELTA; + } + } + + return result; + } + + private void addRange(List ranges, int start, int length, Color foreground, boolean isCode) { + StyleRange range = new StyleRange(start, length, foreground, null); + AnsiConsoleAttributes.updateRangeStyle(range, lastAttributes); + if (isCode) { + range.metrics = new GlyphMetrics(0, 0, 0); + } + ranges.add(range); + lastRangeEnd = lastRangeEnd + range.length; + } + + @Override + public void lineGetStyle(LineStyleEvent event) { + if (event == null || event.lineText == null || event.lineText.length() == 0) + return; + + String currentText = event.lineText; + Matcher matcher = pattern.matcher(currentText); + + // Return directly if the pattern is not found. + if (!matcher.find()) { + return; + } + + StyleRange defStyle; + + if (event.styles != null && event.styles.length > 0) { + defStyle = (StyleRange) event.styles[0].clone(); + if (defStyle.background == null) + defStyle.background = AnsiConsolePreferenceUtils.getDebugConsoleBgColor(); + } else { + defStyle = new StyleRange(1, lastRangeEnd, + new Color(null, AnsiConsoleColorPalette.getColor(0)), + new Color(null, AnsiConsoleColorPalette.getColor(15)), + SWT.NORMAL); + } + + lastRangeEnd = 0; + List ranges = new ArrayList(); + + do { + int start = matcher.start(); + int end = matcher.end(); + + String theEscape = currentText.substring(start + 2, end - 1); + char code = currentText.charAt(end - 1); + if (code == ESCAPE_SGR) { + // Select Graphic Rendition (SGR) escape sequence + List nCommands = new ArrayList(); + for (String cmd : theEscape.split(";")) { + int nCmd = AnsiConsolePreferenceUtils.tryParseInteger(cmd); + if (nCmd != -1) + nCommands.add(nCmd); + } + if (nCommands.isEmpty()) + nCommands.add(0); + interpretCommand(nCommands); + } + + if (lastRangeEnd != start) + addRange(ranges, event.lineOffset + lastRangeEnd, start - lastRangeEnd, defStyle.foreground, false); + lastAttributes = currentAttributes.clone(); + + addRange(ranges, event.lineOffset + start, end - start, defStyle.foreground, true); + } while (matcher.find()); + + if (lastRangeEnd != currentText.length()) + addRange(ranges, event.lineOffset + lastRangeEnd, currentText.length() - lastRangeEnd, defStyle.foreground, false); + lastAttributes = currentAttributes.clone(); + + if (!ranges.isEmpty()) + event.styles = ranges.toArray(new StyleRange[ranges.size()]); + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/diagnostic/utils/PlatformEnvironmentDataCollector.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/diagnostic/utils/PlatformEnvironmentDataCollector.java index 7bf17f5c..44edc600 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/diagnostic/utils/PlatformEnvironmentDataCollector.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/diagnostic/utils/PlatformEnvironmentDataCollector.java @@ -30,7 +30,7 @@ final public class PlatformEnvironmentDataCollector { private static final PlatformDataModel DATA_TEST = getPlatformDataModel(Constants.AWS_TOOLKIT_FOR_ECLIPSE_PRODUCT_NAME_TEST); public static PlatformDataModel getData() { - return AwsToolkitCore.DEBUG_MODE ? DATA_TEST : DATA; + return AwsToolkitCore.getDefault().isDebugMode() ? DATA_TEST : DATA; } private static PlatformDataModel getPlatformDataModel(final String productName) { diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/mobileanalytics/AwsToolkitMetricType.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/mobileanalytics/AwsToolkitMetricType.java index ac34614b..9f4c176d 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/mobileanalytics/AwsToolkitMetricType.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/mobileanalytics/AwsToolkitMetricType.java @@ -90,6 +90,9 @@ public enum AwsToolkitMetricType { LAMBDA_UPLOAD_FUNCTION_WIZARD("Lambda-UploadFunctionWizard"), LAMBDA_INVOKE_FUNCTION_DIALOG("Lambda-InvokeFunctionDialog"), LAMBDA_DEPLOY_SERVERLESS_PROJECT_WIZARD("Lambda-DeployServerlessProjectWizard"), + /* SAM Local Events */ + SAMLOCAL_GENERATE_EVENT("SamLocal-GenerateEvent"), + SAMLOCAL_LAUNCH("SamLocal-Launch"), ; private final String metricName; diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/model/RegionDataModel.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/model/RegionDataModel.java index bdf37af8..0e7758b8 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/model/RegionDataModel.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/model/RegionDataModel.java @@ -32,6 +32,11 @@ public void setRegion(Region region) { this.region = region; } + @JsonProperty + public String getRegionId() { + return region.getId(); + } + @JsonProperty public String getRegionName() { return region.getName(); diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/plugin/AbstractAwsPlugin.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/plugin/AbstractAwsPlugin.java index 0a634d41..fb3e08ab 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/plugin/AbstractAwsPlugin.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/plugin/AbstractAwsPlugin.java @@ -88,4 +88,8 @@ protected ImageRegistry createImageRegistry() { protected Map getImageRegistryMap() { return Collections.emptyMap(); } + + protected String getBundleVersion() { + return getBundle().getVersion().toString(); + } } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/regions/RegionUtils.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/regions/RegionUtils.java index 0fe1dc71..cc2442b0 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/regions/RegionUtils.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/regions/RegionUtils.java @@ -47,19 +47,28 @@ import com.amazonaws.services.s3.model.ObjectMetadata; /** - * Utilities for working with regions. + * Utilities for loading and working with regions. The AWS regions loading priorities are: + * + * {@link #P_REGIONS_FILE_OVERRIDE} // Use the regions file from the provided system property - used for accessing private regions + * > {@link #P_USE_LOCAL_REGION_FILE} // Use the embedded regions file /etc/regions.xml explicitly - used for testing purpose + * > {@link #LOCAL_REGION_FILE_OVERRIDE} // Use the embedded regions file /etc/override.xml if exists - used for accessing private partitions + * > {@link #CLOUDFRONT_DISTRO} // Use the remote shared ServiceEndPoints.xml file - used in most cases for accessing public regions + * > /etc/regions.xml // Use the local embedded file if failed to download the remote file - fall back to the embedded version which could be outdated */ public class RegionUtils { public static final String S3_US_EAST_1_REGIONAL_ENDPOINT = "https://s3-external-1.amazonaws.com"; private static final String CLOUDFRONT_DISTRO = "http://vstoolkit.amazonwebservices.com/"; - private static final String REGIONS_FILE_OVERRIDE = RegionUtils.class.getName() + ".fileOverride"; - public static final String USE_LOCAL_REGION_FILE = RegionUtils.class.getName() + ".useLocalRegionFile"; - private static final String REGIONS_METADATA_S3_BUCKET = "aws-vs-toolkit"; private static final String REGIONS_METADATA_S3_OBJECT = "ServiceEndPoints.xml"; + // System property name whose value is the path of the overriding file. + private static final String P_REGIONS_FILE_OVERRIDE = RegionUtils.class.getName() + ".fileOverride"; + // System property name whose value is a boolean whether to use the embedded region file. + private static final String P_USE_LOCAL_REGION_FILE = RegionUtils.class.getName() + ".useLocalRegionFile"; + // This file overrides the remote ServiceEndPoints.xml file if exists. + private static final String LOCAL_REGION_FILE_OVERRIDE = "/etc/regions-override.xml"; private static final String LOCAL_REGION_FILE = "/etc/regions.xml"; private static List regions; @@ -82,7 +91,7 @@ public static boolean isServiceSupportedInCurrentRegion(String serviceAbbreviati * Returns a list of the available AWS regions. */ public synchronized static List getRegions() { - if ( regions == null ) { + if (regions == null) { init(); } @@ -120,8 +129,8 @@ public synchronized static void addLocalService( */ public synchronized static List getRegionsForService(String serviceAbbreviation) { List regions = new LinkedList<>(); - for ( Region r : getRegions() ) { - if ( r.isServiceSupported(serviceAbbreviation) ) { + for (Region r : getRegions()) { + if (r.isServiceSupported(serviceAbbreviation)) { regions.add(r); } } @@ -132,8 +141,8 @@ public synchronized static List getRegionsForService(String serviceAbbre * Returns the region with the id given, if it exists. Otherwise, returns null. */ public static Region getRegion(String regionId) { - for ( Region r : getRegions() ) { - if ( r.getId().equals(regionId) ) { + for (Region r : getRegions()) { + if (r.getId().equals(regionId)) { return r; } } @@ -199,21 +208,21 @@ public static Region getRegionByEndpoint(String endpoint) { URL targetEndpointUrl = null; try { targetEndpointUrl = new URL(endpoint); - } catch ( MalformedURLException e ) { + } catch (MalformedURLException e) { throw new RuntimeException( "Unable to parse service endpoint: " + e.getMessage()); } String targetHost = targetEndpointUrl.getHost(); - for ( Region region : getRegions() ) { - for ( String serviceEndpoint - : region.getServiceEndpoints().values() ) { + for (Region region : getRegions()) { + for (String serviceEndpoint + : region.getServiceEndpoints().values()) { try { URL serviceEndpointUrl = new URL(serviceEndpoint); - if ( serviceEndpointUrl.getHost().equals(targetHost) ) { + if (serviceEndpointUrl.getHost().equals(targetHost)) { return region; } - } catch ( MalformedURLException e ) { + } catch (MalformedURLException e) { AwsToolkitCore.getDefault().reportException("Unable to parse service endpoint: " + serviceEndpoint, e); } } @@ -230,20 +239,18 @@ public static Region getRegionByEndpoint(String endpoint) { * initializes the static list of regions with it. */ public static synchronized void init() { - - if (System.getProperty(REGIONS_FILE_OVERRIDE) != null) { + // Use overriding file for testing unlaunched services. + if (System.getProperty(P_REGIONS_FILE_OVERRIDE) != null) { loadRegionsFromOverrideFile(); - } else if (!Boolean.valueOf(System.getProperty(USE_LOCAL_REGION_FILE))) { - IPath stateLocation = Platform.getStateLocation(AwsToolkitCore - .getDefault().getBundle()); - File regionsDir = new File(stateLocation.toFile(), "regions"); - File regionsFile = new File(regionsDir, "regions.xml"); - - cacheRegionsFile(regionsFile); - initCachedRegions(regionsFile); + // Use the local region override file + } else if (localRegionOverrideFileExists()) { + initBundledRegionsOverride(); + // Use the remote ServiceEndpoints.xml file + } else if (!Boolean.valueOf(System.getProperty(P_USE_LOCAL_REGION_FILE))) { + initRegionsFromS3(); } // Fall back onto the version we ship with the toolkit - if ( regions == null ) { + if (regions == null) { initBundledRegions(); } @@ -260,21 +267,33 @@ private static void loadRegionsFromOverrideFile() { try { System.setProperty("com.amazonaws.sdk.disableCertChecking", "true"); File regionsFile = - new File(System.getProperty(REGIONS_FILE_OVERRIDE)); - InputStream override = new FileInputStream(regionsFile); - regions = parseRegionMetadata(override); + new File(System.getProperty(P_REGIONS_FILE_OVERRIDE)); + try (InputStream override = new FileInputStream(regionsFile)) { + regions = parseRegionMetadata(override); + } + try { cacheFlags(regionsFile.getParentFile()); - } catch ( Exception e ) { + } catch (Exception e) { AwsToolkitCore.getDefault().logError( "Couldn't cache flag icons", e); } - } catch ( Exception e ) { + } catch (Exception e) { AwsToolkitCore.getDefault().logError( "Couldn't load regions override", e); } } + private static void initRegionsFromS3() { + IPath stateLocation = Platform.getStateLocation(AwsToolkitCore + .getDefault().getBundle()); + File regionsDir = new File(stateLocation.toFile(), "regions"); + File regionsFile = new File(regionsDir, "regions.xml"); + + cacheRegionsFile(regionsFile); + initCachedRegions(regionsFile); + } + /** * Caches the regions file stored in cloudfront to the destination file * given. Tries S3 if cloudfront is unavailable. @@ -283,7 +302,7 @@ private static void loadRegionsFromOverrideFile() { */ private static void cacheRegionsFile(File regionsFile) { Date regionsFileLastModified = new Date(0); - if ( !regionsFile.exists() ) { + if (!regionsFile.exists()) { regionsFile.getParentFile().mkdirs(); } else { regionsFileLastModified = new Date(regionsFile.lastModified()); @@ -294,11 +313,11 @@ private static void cacheRegionsFile(File regionsFile) { AWSClientFactory.getAnonymousS3Client(); ObjectMetadata objectMetadata = s3.getObjectMetadata(REGIONS_METADATA_S3_BUCKET, REGIONS_METADATA_S3_OBJECT); - if ( objectMetadata.getLastModified() - .after(regionsFileLastModified) ) { + if (objectMetadata.getLastModified() + .after(regionsFileLastModified)) { cacheRegionsFile(regionsFile, s3); } - } catch ( Exception e ) { + } catch (Exception e) { AwsToolkitCore.getDefault().logError( "Failed to cache regions file", e); } @@ -310,16 +329,15 @@ private static void cacheRegionsFile(File regionsFile) { * on the next startup. */ private static void initCachedRegions(File regionsFile) { - try { - InputStream inputStream = new FileInputStream(regionsFile); + try (InputStream inputStream = new FileInputStream(regionsFile)) { regions = parseRegionMetadata(inputStream); try { cacheFlags(regionsFile.getParentFile()); - } catch ( Exception e ) { + } catch (Exception e) { AwsToolkitCore.getDefault().logError( "Couldn't cache flag icons", e); } - } catch ( Exception e ) { + } catch (Exception e) { AwsToolkitCore.getDefault().logError( "Couldn't read regions file", e); // Clear out the regions file so that it will get cached again at @@ -333,11 +351,28 @@ private static void initCachedRegions(File regionsFile) { * the plugin, in case it cannot be fetched from the remote source. */ private static void initBundledRegions() { - ClassLoader classLoader = RegionUtils.class.getClassLoader(); - InputStream inputStream = - classLoader.getResourceAsStream(LOCAL_REGION_FILE); - regions = parseRegionMetadata(inputStream); - for ( Region r : regions ) { + try { + regions = loadRegionsFromLocalRegionFile(); + registerRegionFlagsFromBundle(regions); + } catch (IOException e) { + // Do nothing, the regions remains null in this case. + } + } + + private static void initBundledRegionsOverride() { + try { + regions = loadRegionsFromLocalRegionOverrideFile(); + registerRegionFlagsFromBundle(regions); + } catch (IOException e) { + // Do nothing, the regions remains null in this case. + } + } + + private static final void registerRegionFlagsFromBundle(List regions) { + if (regions == null) { + return; + } + for (Region r : regions) { if (r == LocalRegion.INSTANCE) { // No flag to load for the local region. continue; @@ -360,6 +395,9 @@ private static void initBundledRegions() { * special "local" region. */ private static List parseRegionMetadata(InputStream inputStream) { + if (inputStream == null) { + return null; + } List list = PARSER.parseRegionMetadata(inputStream); list.add(LocalRegion.INSTANCE); replaceS3GlobalEndpointWithRegional(list); @@ -396,10 +434,10 @@ private static void cacheRegionsFile(File regionsFile, AmazonS3 s3) { */ private static void truncateFile(File file) throws FileNotFoundException, IOException { - if ( file.exists() ) { - RandomAccessFile raf = new RandomAccessFile(file, "rw"); - raf.getChannel().truncate(0); - raf.close(); + if (file.exists()) { + try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + raf.getChannel().truncate(0); + } } } @@ -408,18 +446,18 @@ private static void truncateFile(File file) */ private static void cacheFlags(File regionsDir) throws ClientProtocolException, IOException { - if ( !regionsDir.exists() ) { + if (!regionsDir.exists()) { return; } - for ( Region r : regions ) { + for (Region r : regions) { if (r == LocalRegion.INSTANCE) { // Local region has no flag to initialize. continue; } File icon = new File(regionsDir, r.getFlagIconPath()); - if ( icon.exists() == false ) { + if (icon.exists() == false) { icon.getParentFile().mkdirs(); String iconUrl = CLOUDFRONT_DISTRO + r.getFlagIconPath(); fetchFile(iconUrl, icon); @@ -446,18 +484,13 @@ private static void fetchFile(String url, File destinationFile) HttpGet httpget = new HttpGet(url); HttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); - if ( entity != null ) { - InputStream instream = entity.getContent(); - FileOutputStream output = new FileOutputStream(destinationFile); - try { + if (entity != null) { + try (InputStream instream = entity.getContent(); FileOutputStream output = new FileOutputStream(destinationFile)) { int l; byte[] tmp = new byte[2048]; - while ( (l = instream.read(tmp)) != -1 ) { + while ((l = instream.read(tmp)) != -1) { output.write(tmp, 0, l); } - } finally { - output.close(); - instream.close(); } } } @@ -465,25 +498,36 @@ private static void fetchFile(String url, File destinationFile) /** * Load regions from remote S3 bucket. */ - public static List loadRegionsFromS3() { + public static List loadRegionsFromS3() throws IOException { AmazonS3 s3 = AWSClientFactory.getAnonymousS3Client(); - InputStream inputStream = + try (InputStream inputStream = s3.getObject(REGIONS_METADATA_S3_BUCKET, REGIONS_METADATA_S3_OBJECT) - .getObjectContent(); - return parseRegionMetadata(inputStream); + .getObjectContent()) { + return parseRegionMetadata(inputStream); + } } /** * Load regions from local file. */ - public static List loadRegionsFromLocalFile() { + private static List loadRegionsFromLocalFile(String localFileName) throws IOException { ClassLoader classLoader = RegionUtils.class.getClassLoader(); - InputStream inputStream = - classLoader.getResourceAsStream(LOCAL_REGION_FILE); - return parseRegionMetadata(inputStream); + try (InputStream inputStream = + classLoader.getResourceAsStream(localFileName)) { + return parseRegionMetadata(inputStream); + } + } + + private static boolean localRegionOverrideFileExists() { + ClassLoader classLoader = RegionUtils.class.getClassLoader(); + return classLoader.getResource(LOCAL_REGION_FILE_OVERRIDE) != null; + } + + private static List loadRegionsFromLocalRegionOverrideFile() throws IOException { + return loadRegionsFromLocalFile(LOCAL_REGION_FILE_OVERRIDE); } - public static void useBuiltInRegionFile() { - System.setProperty(USE_LOCAL_REGION_FILE, "true"); + public static List loadRegionsFromLocalRegionFile() throws IOException { + return loadRegionsFromLocalFile(LOCAL_REGION_FILE); } } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/AccountSelectionComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/AccountSelectionComposite.java index 0ce2ab8f..26d8eaf6 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/AccountSelectionComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/AccountSelectionComposite.java @@ -63,6 +63,7 @@ public void addSelectionListener(SelectionListener listner) { public AccountSelectionComposite(final Composite parent, final int style) { super(parent, style); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); setLayout(new GridLayout(3, false)); createChildWidgets(); @@ -90,7 +91,7 @@ protected void createNoAccountLabel() { protected void createAccountConfigurationLink() { Link link = new Link(this, SWT.NONE); link.setFont(this.getFont()); - link.setText("" + "Configure AWS accounts..." + ""); //$NON-NLS-1$ //$NON-NLS-2$ + link.setText("" + "Configure AWS profiles..." + ""); link.addSelectionListener(new SelectionListener() { @@ -121,7 +122,7 @@ public void widgetDefaultSelected(final SelectionEvent e) { protected void createAccountSelectionCombo() { Label selectAccount = new Label(this, SWT.None); - selectAccount.setText("Select Account:"); //$NON-NLS-1$ + selectAccount.setText("Select profile:"); this.accountSelection = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ImportFileComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ImportFileComposite.java index 9dbfec99..9f67306f 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ImportFileComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ImportFileComposite.java @@ -18,9 +18,22 @@ import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newPushButton; import org.eclipse.core.databinding.DataBindingContext; -import org.eclipse.core.databinding.beans.PojoObservables; +import org.eclipse.core.databinding.beans.PojoProperties; import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.JavaElementLabelProvider; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; @@ -28,26 +41,31 @@ import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.ui.dialogs.ContainerSelectionDialog; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; +import org.eclipse.ui.model.BaseWorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.model.ImportFileDataModel; +import com.amazonaws.eclipse.core.validator.NoopValidator; import com.amazonaws.eclipse.core.widget.TextComplex; /** * A reusable File import widget composite. */ public class ImportFileComposite extends Composite { - private TextComplex filePathComplex; private Button browseButton; - private final IValidator filePathValidator; - public ImportFileComposite(Composite parent, DataBindingContext context, - ImportFileDataModel dataModel, IValidator validator) { + private ImportFileComposite(Composite parent, DataBindingContext context, + ImportFileDataModel dataModel, IValidator validator, String textLabel, + String buttonLabel, ModifyListener modifyListener, String textMessage) { super(parent, SWT.NONE); - filePathValidator = validator; - setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); setLayout(new GridLayout(3, false)); - createControl(context, dataModel); + createControl(context, dataModel, validator, textLabel, buttonLabel, modifyListener, textMessage); } @Override @@ -56,25 +74,201 @@ public void setEnabled(boolean enabled) { browseButton.setEnabled(enabled); } - private void createControl(DataBindingContext context, ImportFileDataModel dataModel) { - filePathComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, P_FILE_PATH)) - .labelValue("Import:") + @NonNull + public static ImportFileCompositeBuilder builder( + @NonNull Composite parent, + @NonNull DataBindingContext dataBindingContext, + @NonNull ImportFileDataModel dataModel) { + return new ImportFileCompositeBuilder(parent, dataBindingContext, dataModel); + } + + private void createControl(DataBindingContext context, ImportFileDataModel dataModel, + IValidator filePathValidator, String textLabel, String buttonLabel, + ModifyListener modifyListener, String textMessage) { + + filePathComplex = TextComplex.builder(this, context, PojoProperties.value(P_FILE_PATH).observe(dataModel)) + .labelValue(textLabel) .addValidator(filePathValidator) .defaultValue(dataModel.getFilePath()) + .modifyListener(modifyListener) + .textMessage(textMessage) .build(); - browseButton = newPushButton(this, "Browse"); + browseButton = newPushButton(this, buttonLabel); + } + + public void setFilePath(String filePath) { + filePathComplex.setText(filePath); + } + + /** + * The browse Button opens a general file selection dialog. + */ + private void setButtonListenerToBrowseFile() { browseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { FileDialog dialog = new FileDialog(getShell(), SWT.SINGLE); String path = dialog.open(); - if (path != null) filePathComplex.setText(path); + if (path != null) { + filePathComplex.setText(path); + } + } + }); + } + + /** + * The browse Button opens a dialog to only allow to select folders under the workspace. + */ + private void setButtonListenerToBrowseWorkspaceDir() { + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + ContainerSelectionDialog dialog = new ContainerSelectionDialog(getShell(), + ResourcesPlugin.getWorkspace().getRoot(), false, "Choose Base Directory"); + dialog.showClosedProjects(false); + + int buttonId = dialog.open(); + if (buttonId == IDialogConstants.OK_ID) { + Object[] resource = dialog.getResult(); + if (resource != null && resource.length > 0) { + String fileLoc = VariablesPlugin.getDefault().getStringVariableManager() + .generateVariableExpression("workspace_loc", ((IPath) resource[0]).toString()); + filePathComplex.setText(fileLoc); + } + } } }); } + /** + * The browse Button opens a dialog to only allow to select files under the workspace. + */ + private void setButtonListenerToBrowseWorkspaceFile() { + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), + new WorkbenchLabelProvider(), new BaseWorkbenchContentProvider()); + dialog.setTitle("Choose File"); + dialog.setInput(ResourcesPlugin.getWorkspace().getRoot()); + + int buttonId = dialog.open(); + if (buttonId == IDialogConstants.OK_ID) { + Object[] resource = dialog.getResult(); + if (resource != null && resource.length > 0) { + String fileLoc = VariablesPlugin.getDefault().getStringVariableManager() + .generateVariableExpression("workspace_loc", + ((IResource) resource[0]).getFullPath().toString()); + filePathComplex.setText(fileLoc); + } + } + } + }); + } + + /** + * The browse Button opens a dialog to only allow to select project under the workspace. + */ + private void setButtonListenerToBrowseWorkspaceProject() { + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + ILabelProvider labelProvider= new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT); + ElementListSelectionDialog dialog= new ElementListSelectionDialog(getShell(), labelProvider); + dialog.setTitle("Choose project"); + dialog.setMessage("Choose the project for the job"); + try { + dialog.setElements(JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()).getJavaProjects()); + } catch (JavaModelException jme) { + AwsToolkitCore.getDefault().logError(jme.getMessage(), jme); + } + if (dialog.open() == Window.OK) { + filePathComplex.setText(((IJavaProject) dialog.getFirstResult()).getElementName()); + } + } + }); + } + + public static final class ImportFileCompositeBuilder { + private final Composite parent; + private final DataBindingContext context; + private final ImportFileDataModel dataModel; + + // Optional parameters + private IValidator filePathValidator = new NoopValidator(); + private ModifyListener modifyListener; + private String textLabel = "Import:"; + private String buttonLabel = "Browse"; + private String textMessage = ""; + + private ImportFileCompositeBuilder( + @NonNull Composite parent, + @NonNull DataBindingContext context, + @NonNull ImportFileDataModel dataModel) { + this.parent = parent; + this.context = context; + this.dataModel = dataModel; + } + + public ImportFileCompositeBuilder filePathValidator(IValidator filePathValidator) { + this.filePathValidator = filePathValidator; + return this; + } + + public ImportFileCompositeBuilder textLabel(String textLabel) { + this.textLabel = textLabel; + return this; + } + + public ImportFileCompositeBuilder buttonLabel(String buttonLabel) { + this.buttonLabel = buttonLabel; + return this; + } + + public ImportFileCompositeBuilder modifyListener(ModifyListener modifyListener) { + this.modifyListener = modifyListener; + return this; + } + + public ImportFileCompositeBuilder textMessage(String textMessage) { + this.textMessage = textMessage; + return this; + } + + /** + * Build a general file importer component. + */ + public ImportFileComposite build() { + ImportFileComposite composite = new ImportFileComposite( + parent, context, dataModel, + filePathValidator, textLabel, buttonLabel, modifyListener, textMessage); + composite.setButtonListenerToBrowseFile(); + return composite; + } + + public ImportFileComposite buildWorkspaceDirBrowser() { + ImportFileComposite composite = new ImportFileComposite( + parent, context, dataModel, + filePathValidator, textLabel, buttonLabel, modifyListener, textMessage); + composite.setButtonListenerToBrowseWorkspaceDir(); + return composite; + } + + public ImportFileComposite buildWorkspaceFileBrowser() { + ImportFileComposite composite = new ImportFileComposite( + parent, context, dataModel, + filePathValidator, textLabel, buttonLabel, modifyListener, textMessage); + composite.setButtonListenerToBrowseWorkspaceFile(); + return composite; + } + + public ImportFileComposite buildWorkspaceProjectBrowser() { + ImportFileComposite composite = new ImportFileComposite( + parent, context, dataModel, + filePathValidator, textLabel, buttonLabel, modifyListener, textMessage); + composite.setButtonListenerToBrowseWorkspaceProject(); + return composite; + } + } } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/KeyValueSetEditingComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/KeyValueSetEditingComposite.java index 1bc8cbb5..249457c3 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/KeyValueSetEditingComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/KeyValueSetEditingComposite.java @@ -426,23 +426,17 @@ protected Control createDialogArea(Composite parent) { GridLayout layout = new GridLayout(2, false); container.setLayout(layout); - keyText = TextComplex.builder() - .composite(container) - .dataBindingContext(dataBindingContext) + keyText = TextComplex.builder(container, dataBindingContext, PojoObservables.observeValue(pairModel, Pair.P_KEY)) .labelValue(keyLabel) .defaultValue(pairModel.getKey()) - .pojoObservableValue(PojoObservables.observeValue(pairModel, Pair.P_KEY)) .addValidator(new NotEmptyValidator("The key name cannot be empty.")) .addValidator(new KeyNotDuplicateValidator(pairSet, pairModel, "This field must not contain duplicate items.")) .addValidators(keyValidators) .build(); - valueText = TextComplex.builder() - .composite(container) - .dataBindingContext(dataBindingContext) + valueText = TextComplex.builder(container, dataBindingContext, PojoObservables.observeValue(pairModel, Pair.P_VALUE)) .labelValue(valueLabel) .defaultValue(pairModel.getValue()) - .pojoObservableValue(PojoObservables.observeValue(pairModel, Pair.P_VALUE)) .addValidators(valueValidators) .build(); diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/MavenConfigurationComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/MavenConfigurationComposite.java index bd3881d8..f0c13f33 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/MavenConfigurationComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/MavenConfigurationComposite.java @@ -14,10 +14,14 @@ */ package com.amazonaws.eclipse.core.ui; +import static com.amazonaws.eclipse.core.model.MavenConfigurationDataModel.P_ARTIFACT_ID; +import static com.amazonaws.eclipse.core.model.MavenConfigurationDataModel.P_GROUP_ID; +import static com.amazonaws.eclipse.core.model.MavenConfigurationDataModel.P_PACKAGE_NAME; +import static com.amazonaws.eclipse.core.model.MavenConfigurationDataModel.P_VERSION; + import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.beans.PojoObservables; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; @@ -45,43 +49,31 @@ public MavenConfigurationComposite(Composite parent, DataBindingContext context, } private void createControl(DataBindingContext context, MavenConfigurationDataModel dataModel) { - groupIdComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, MavenConfigurationDataModel.P_GROUP_ID)) + groupIdComplex = TextComplex.builder(this, context, PojoObservables.observeValue(dataModel, P_GROUP_ID)) .addValidator(new NotEmptyValidator("Group ID must be provided!")) - .modifyListener((e) -> { + .modifyListener(e -> { onMavenConfigurationChange(); }) .labelValue("Group ID:") .defaultValue(dataModel.getGroupId()) .build(); - artifactIdComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, MavenConfigurationDataModel.P_ARTIFACT_ID)) + artifactIdComplex = TextComplex.builder(this, context, PojoObservables.observeValue(dataModel, P_ARTIFACT_ID)) .addValidator(new NotEmptyValidator("Artifact ID must be provided!")) - .modifyListener((e) -> { + .modifyListener(e -> { onMavenConfigurationChange(); }) .labelValue("Artifact ID:") .defaultValue(dataModel.getArtifactId()) .build(); - versionComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, MavenConfigurationDataModel.P_VERSION)) + versionComplex = TextComplex.builder(this, context, PojoObservables.observeValue(dataModel, P_VERSION)) .addValidator(new NotEmptyValidator("Version must be provided!")) .labelValue("Version:") .defaultValue(dataModel.getVersion()) .build(); - packageComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, MavenConfigurationDataModel.P_PACKAGE_NAME)) + packageComplex = TextComplex.builder(this, context, PojoObservables.observeValue(dataModel, P_PACKAGE_NAME)) .addValidator(new PackageNameValidator("Package name must be provided!")) .labelValue("Package name:") .defaultValue(dataModel.getPackageName()) diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ProjectNameComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ProjectNameComposite.java index 415a0aec..ea461ccf 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ProjectNameComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/ProjectNameComposite.java @@ -14,6 +14,8 @@ */ package com.amazonaws.eclipse.core.ui; +import static com.amazonaws.eclipse.core.model.ProjectNameDataModel.P_PROJECT_NAME; + import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.beans.PojoObservables; import org.eclipse.swt.SWT; @@ -40,10 +42,7 @@ public ProjectNameComposite(Composite parent, DataBindingContext context, Projec } private void createControl(DataBindingContext context, ProjectNameDataModel dataModel) { - projectNameComplex = TextComplex.builder() - .composite(this) - .dataBindingContext(context) - .pojoObservableValue(PojoObservables.observeValue(dataModel, ProjectNameDataModel.P_PROJECT_NAME)) + projectNameComplex = TextComplex.builder(this, context, PojoObservables.observeValue(dataModel, P_PROJECT_NAME)) .addValidator(new ProjectNameValidator()) .defaultValue(dataModel.getProjectName()) .labelValue("Project name:") diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/RegionComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/RegionComposite.java index f202ccf9..71ecf99a 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/RegionComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/RegionComposite.java @@ -62,8 +62,8 @@ private RegionComposite( this.serviceName = serviceName; this.labelValue = labelValue; this.listeners = listeners; - this.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - this.setLayout(new GridLayout(3, true)); + this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + this.setLayout(new GridLayout(3, false)); createControl(); } @@ -110,7 +110,7 @@ public static final class RegionCompositeBuilder { private DataBindingContext bindingContext; private RegionDataModel dataModel; private String serviceName; - private String labelValue = "Select Regions:"; + private String labelValue = "Select region:"; private final List listeners = new ArrayList<>(); public RegionComposite build() { diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/SelectOrInputComposite.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/SelectOrInputComposite.java index aceecbe6..7d1d17ea 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/SelectOrInputComposite.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/SelectOrInputComposite.java @@ -184,12 +184,8 @@ public void widgetSelected(SelectionEvent e) { }) .build(); - this.createText = TextComplex.builder() - .composite(this) + this.createText = TextComplex.builder(this, bindingContext, PojoProperties.value(P_NEW_RESOURCE_NAME).observe(dataModel)) .createLabel(false) - .dataBindingContext(bindingContext) - .pojoObservableValue(PojoProperties.value(P_NEW_RESOURCE_NAME, String.class) - .observe(dataModel)) .addValidators(createTextValidators) .build(); } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/wizards/WizardWidgetFactory.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/wizards/WizardWidgetFactory.java index 1aa11ee6..85abfd9b 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/wizards/WizardWidgetFactory.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/ui/wizards/WizardWidgetFactory.java @@ -158,6 +158,7 @@ public static ComboViewer newComboViewer(Composite parent, int colspan) { GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); gridData.horizontalSpan = colspan; comboViewer.getCombo().setLayoutData(gridData); + return comboViewer; } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/AbstractApplicationLauncher.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/AbstractApplicationLauncher.java new file mode 100644 index 00000000..2ca48c38 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/AbstractApplicationLauncher.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 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.core.util; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.ui.DebugUITools; + +/** + * Super class for launching an application. + * + * @see MavenBuildLauncher + * @see RemoteDebugLauncher + */ +public abstract class AbstractApplicationLauncher { + protected long timeIntervalMilli = 5000L; + protected final String mode; + protected final IProgressMonitor monitor; + + protected AbstractApplicationLauncher(String mode, IProgressMonitor monitor) { + this.mode = mode; + this.monitor = monitor; + } + + /** + * Validate the required parameters for launching this application. + * @throws IllegalArgumentException - If the required parameters are not provided or invalid. + */ + protected abstract void validateParameters() throws IllegalArgumentException; + + /** + * Create a new {@link ILaunchConfiguration} for this application. + */ + protected abstract ILaunchConfiguration createLaunchConfiguration() throws CoreException; + + public final ILaunch launchAsync() throws CoreException { + validateParameters(); + ILaunchConfiguration launchConfiguration = createLaunchConfiguration(); + return DebugUITools.buildAndLaunch(launchConfiguration, mode, monitor); + } + + /** + * Blocking call for launching the application. This call blocks until the application terminates. + */ + public final ILaunch launch() throws CoreException { + ILaunch launch = launchAsync(); + while (!launch.isTerminated()) { + try { + Thread.sleep(timeIntervalMilli); + } catch (InterruptedException e) { + } + } + return launch; + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/CliUtil.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/CliUtil.java new file mode 100644 index 00000000..bd3a08e3 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/CliUtil.java @@ -0,0 +1,115 @@ +/* + * Copyright 2017 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.core.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * Utility for calling a CLI command, and managing the input/output of the process. + */ +public class CliUtil { + + public static CliProcessTracker executeCommand(List commandLine, Map envp, OutputStream stdOut, OutputStream stdErr) + throws IOException { + Process process = buildProcess(commandLine, envp); + PipeThread stdInputOutput = new PipeThread("CLI stdout", process.getInputStream(), stdOut); + PipeThread stdErrThread = new PipeThread("CLI stderr", process.getErrorStream(), stdErr); + stdInputOutput.start(); + stdErrThread.start(); + return new CliProcessTracker(process, stdInputOutput, stdErrThread); + } + + /** + * Using {@link ProcessBuilder} to build a {@link Process}. {@link ProcessBuilder} aggregates a copy of the current process environment + * so that we don't have to manually copy these. + * + * @see {@link ProcessBuilder#environment()} + */ + public static Process buildProcess(List commandLine, Map envp) throws IOException { + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + processBuilder.environment().putAll(envp); + return processBuilder.start(); + } + + /** + * Tracks the process along with its two stream threads. + */ + public static class CliProcessTracker { + private final Process process; + private final PipeThread stdOutThread; + private final PipeThread stdErrThread; + + public CliProcessTracker(Process process, PipeThread stdOutThread, PipeThread stdErrThread) { + this.process = process; + this.stdOutThread = stdOutThread; + this.stdErrThread = stdErrThread; + } + + public Process getProcess() { + return process; + } + + /** + * Destroy the process and wait for the Stream threads to finish. + */ + public void destroy() { + process.destroy(); + waitForStream(); + } + + /** + * Wait for the Stream threads to finish. Ie. to drain the input streams, and return the exit code. + */ + public int waitForStream() { + try { + stdOutThread.join(); + stdErrThread.join(); + } catch (InterruptedException e) { + } + return process.exitValue(); + } + } + + /** + * A thread that keeps reading from a target InputStream, and writes to an OutputStream. + */ + private static class PipeThread extends Thread { + private final InputStream inputStream; + private final OutputStream outputStream; + + private PipeThread(String name, InputStream inputStream, OutputStream outputStream) { + this.inputStream = inputStream; + this.outputStream = outputStream; + this.setName(name); + } + + @Override + public void run() { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line = null; + while ((line = reader.readLine()) != null) { + outputStream.write(String.format("%s%n", line).getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) {} + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/MavenBuildLauncher.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/MavenBuildLauncher.java new file mode 100644 index 00000000..55dd6eb5 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/MavenBuildLauncher.java @@ -0,0 +1,137 @@ +/* + * Copyright 2017 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.core.util; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.ui.IDebugUIConstants; +import org.eclipse.debug.ui.RefreshTab; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.m2e.actions.MavenLaunchConstants; +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.m2e.core.project.IMavenProjectFacade; +import org.eclipse.m2e.core.project.IMavenProjectRegistry; +import org.eclipse.m2e.core.project.ResolverConfiguration; + +/** + * Utility class for launching a customized Maven build. + * + * @see org.eclipse.m2e.actions.ExecutePomAction + */ +public class MavenBuildLauncher extends AbstractApplicationLauncher { + private static final String POM_FILE_NAME = "pom.xml"; + + private static final String executePomActionExecutingMessage(String goals, String projectLocation) { + return String.format("Executing %s in %s", goals, projectLocation); + } + + private final IProject project; + private final String goals; + + public MavenBuildLauncher(IProject project, String goals, IProgressMonitor monitor) { + super(ILaunchManager.RUN_MODE, monitor); + this.project = project; + this.goals = goals; + } + + @Override + protected ILaunchConfiguration createLaunchConfiguration() throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType launchConfigurationType = launchManager + .getLaunchConfigurationType(MavenLaunchConstants.LAUNCH_CONFIGURATION_TYPE_ID); + + String rawConfigName = executePomActionExecutingMessage(goals, project.getLocation().toString()); + String safeConfigName = launchManager.generateLaunchConfigurationName(rawConfigName); + + ILaunchConfigurationWorkingCopy workingCopy = launchConfigurationType.newInstance(null, safeConfigName); + workingCopy.setAttribute(MavenLaunchConstants.ATTR_POM_DIR, project.getLocation().toOSString()); + workingCopy.setAttribute(MavenLaunchConstants.ATTR_GOALS, goals); + workingCopy.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); + workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_SCOPE, "${project}"); + workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_RECURSIVE, true); + + setProjectConfiguration(workingCopy, project); + + IPath path = getJREContainerPath(project); + if (path != null) { + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, + path.toPortableString()); + } + + return workingCopy; + } + + private void setProjectConfiguration(ILaunchConfigurationWorkingCopy workingCopy, IContainer basedir) { + IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry(); + IFile pomFile = basedir.getFile(new Path(POM_FILE_NAME)); + IMavenProjectFacade projectFacade = projectManager.create(pomFile, false, new NullProgressMonitor()); + if (projectFacade != null) { + ResolverConfiguration configuration = projectFacade.getResolverConfiguration(); + + String selectedProfiles = configuration.getSelectedProfiles(); + if (selectedProfiles != null && selectedProfiles.length() > 0) { + workingCopy.setAttribute(MavenLaunchConstants.ATTR_PROFILES, selectedProfiles); + } + } + } + + private IPath getJREContainerPath(IContainer basedir) throws CoreException { + IProject project = basedir.getProject(); + if (project != null && project.hasNature(JavaCore.NATURE_ID)) { + IJavaProject javaProject = JavaCore.create(project); + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (int i = 0; i < entries.length; i++) { + IClasspathEntry entry = entries[i]; + if (JavaRuntime.JRE_CONTAINER.equals(entry.getPath().segment(0))) { + return entry.getPath(); + } + } + } + return null; + } + + /** + * Validate all the provided parameters to perform a launch. + * + * @throws IllegalArgumentException if any parameter is not legal. + */ + @Override + protected void validateParameters() { + if (project == null) { + throw new IllegalArgumentException("The provided project cannot be null!"); + } + if (project.findMember(POM_FILE_NAME) == null) { + throw new IllegalArgumentException("The project must be a Maven project with a " + POM_FILE_NAME + " file in the root!"); + } + if (goals == null || goals.isEmpty()) { + throw new IllegalArgumentException("The goals specified must not be empty!"); + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/OsPlatformUtils.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/OsPlatformUtils.java new file mode 100644 index 00000000..71236634 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/OsPlatformUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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.core.util; + +import org.eclipse.core.runtime.Platform; + +public class OsPlatformUtils { + + public static boolean isWindows() { + return Platform.getOS().equals(Platform.OS_WIN32); + } + + public static boolean isMac() { + return Platform.getOS().equals(Platform.OS_MACOSX); + } + + public static boolean isLinux() { + return Platform.getOS().equals(Platform.OS_LINUX); + } + + public static String currentUser() { + return System.getProperty("user.name"); + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/PluginUtils.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/PluginUtils.java new file mode 100644 index 00000000..c415dd3b --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/PluginUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 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.core.util; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsoleManager; +import org.eclipse.ui.console.MessageConsole; + +/** + * Utilities for invoking other plugin features. + */ +public class PluginUtils { + + /** + * Get or create a new MessageConsole given the console name. + */ + public static MessageConsole getOrCreateMessageConsole(String consoleName) { + IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager(); + + // Search existing consoles + IConsole[] consoles = consoleManager.getConsoles(); + if (consoles != null) { + for (IConsole console : consoles) { + if (consoleName.equals(console.getName()) + && (console instanceof MessageConsole)) { + return (MessageConsole)console; + } + } + } + + // If not found, create a new console + MessageConsole newConsole = new MessageConsole(consoleName, null); + ConsolePlugin.getDefault().getConsoleManager() + .addConsoles(new IConsole[] { newConsole }); + return newConsole; + } + + /** + * Replace the placeholder variables in the original string with the real ones. + * E.g. ${workspace_loc:/Project/file.txt} will be replaced to the real workspace location. + * + * @throws CoreException - If unable to resolve the value of one or more variables + */ + public static String variablePluginReplace(String originalValue) throws CoreException { + if (originalValue == null || originalValue.isEmpty()) { + return originalValue; + } + return VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(originalValue); + } + + private static final String WORKSPACE_LOC = "workspace_loc"; + /** + * Generate a String with workspace location variable, and the relative path argument. + */ + public static String variablePluginGenerateWorkspacePath(String argument) { + return VariablesPlugin.getDefault().getStringVariableManager().generateVariableExpression(WORKSPACE_LOC, argument); + } + + public static String variablePluginGenerateWorkspacePath(IPath relativePath) { + return variablePluginGenerateWorkspacePath(relativePath.toString()); + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/RemoteDebugLauncher.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/RemoteDebugLauncher.java new file mode 100644 index 00000000..2e76ea9e --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/RemoteDebugLauncher.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 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.core.util; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; + +/** + * Launch a remote debugger. + * + * @see org.eclipse.m2e.internal.launch.MavenConsoleLineTracker + */ +public class RemoteDebugLauncher extends AbstractApplicationLauncher { + private final IProject project; + private final int portNo; + + public RemoteDebugLauncher(IProject project, int port, IProgressMonitor monitor) { + super(ILaunchManager.DEBUG_MODE, monitor); + this.project = project; + this.portNo = port; + } + + @Override + protected ILaunchConfiguration createLaunchConfiguration() throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType launchConfigurationType = launchManager + .getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_REMOTE_JAVA_APPLICATION); + + ILaunchConfigurationWorkingCopy workingCopy = launchConfigurationType.newInstance(null, + "Connecting debugger to port " + portNo); + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true); + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_CONNECTOR, + IJavaLaunchConfigurationConstants.ID_SOCKET_ATTACH_VM_CONNECTOR); + + Map connectMap = new HashMap(); + connectMap.put("port", String.valueOf(portNo)); + connectMap.put("hostname", "localhost"); + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CONNECT_MAP, connectMap); + + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getName()); + + return workingCopy; + } + + @Override + protected void validateParameters() { + if (project == null) { + throw new IllegalArgumentException("The project must be specified!"); + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/WorkbenchUtils.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/WorkbenchUtils.java index bc107c66..3c377110 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/WorkbenchUtils.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/util/WorkbenchUtils.java @@ -77,7 +77,10 @@ public void run() { try { if (url == null || workbench == null) return; IWorkbenchBrowserSupport browserSupport = workbench.getBrowserSupport(); - browserSupport.createBrowser(IWorkbenchBrowserSupport.AS_EDITOR, null, null, null) + browserSupport.createBrowser( + IWorkbenchBrowserSupport.AS_EDITOR + | IWorkbenchBrowserSupport.LOCATION_BAR + | IWorkbenchBrowserSupport.NAVIGATION_BAR, null, null, null) .openURL(url); } catch (PartInitException e) { AwsToolkitCore.getDefault().logWarning(String.format("Failed to open the url %s in the editor!", url.toString()), e); diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/FilePathValidator.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/FilePathValidator.java index 07d13026..a2d3d67e 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/FilePathValidator.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/FilePathValidator.java @@ -21,9 +21,14 @@ import org.eclipse.core.runtime.IStatus; /** - * + * File exists and valid validator. */ public class FilePathValidator implements IValidator { + private final String propertyName; + + public FilePathValidator(String propertyName) { + this.propertyName = propertyName; + } /* (non-Javadoc) * @see org.eclipse.core.databinding.validation.IValidator#validate(java.lang.Object) @@ -33,9 +38,8 @@ public IStatus validate(Object value) { String filePath = (String) value; File file = new File(filePath); if (!file.exists() || !file.isFile()) { - return ValidationStatus.error("Not a valid file!"); + return ValidationStatus.error("Property " + propertyName + " does not contain a valid file!"); } return ValidationStatus.ok(); } - } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/IntegerRangeValidator.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/IntegerRangeValidator.java new file mode 100644 index 00000000..da506784 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/IntegerRangeValidator.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.core.validator; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.IStatus; + +public class IntegerRangeValidator implements IValidator { + private final String propertyName; + private final int min; + private final int max; + + public IntegerRangeValidator(String propertyName, int min, int max) { + this.propertyName = propertyName; + this.min = min; + this.max = max; + } + + @Override + public IStatus validate(final Object value) { + if (!(value instanceof String)) { + return ValidationStatus.error(propertyName + " value must be specified!"); + } + + int number; + try { + number = Integer.parseInt((String) value); + } catch (NumberFormatException exception) { + return ValidationStatus.error(propertyName + " value must be an integer!"); + } + + if (number < min || number > max) { + return ValidationStatus.error(String.format("%s value must be between %d and %d", propertyName, min, max)); + } + + return ValidationStatus.ok(); + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/WorkspacePathValidator.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/WorkspacePathValidator.java new file mode 100644 index 00000000..706233b9 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/validator/WorkspacePathValidator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 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.core.validator; + +import java.io.File; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; + +import com.amazonaws.eclipse.core.util.PluginUtils; + +/** + * Validator that validates the provided path is valid and the underlying file exists. + */ +public class WorkspacePathValidator implements IValidator { + private final String propertyName; + private final boolean isEmptyAllowed; + + public WorkspacePathValidator(String propertyName, boolean isEmptyAllowed) { + this.propertyName = propertyName; + this.isEmptyAllowed = isEmptyAllowed; + } + + /* (non-Javadoc) + * @see org.eclipse.core.databinding.validation.IValidator#validate(java.lang.Object) + */ + @Override + public IStatus validate(Object value) { + try { + String filePath = (String) value; + if (filePath == null || filePath.isEmpty()) { + if (isEmptyAllowed) { + return ValidationStatus.ok(); + } else { + return ValidationStatus.error(propertyName + " value must not be empty."); + } + } + filePath = PluginUtils.variablePluginReplace(filePath); + File file = new File(filePath); + if (!file.exists() || !file.isFile()) { + return ValidationStatus.error(propertyName + " value is not a valid file!"); + } + return ValidationStatus.ok(); + } catch (CoreException e) { + return ValidationStatus.error(propertyName + " value is not a valid file!"); + } + } +} diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/ComboViewerComplex.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/ComboViewerComplex.java index e8eef46f..dd5f26fa 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/ComboViewerComplex.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/ComboViewerComplex.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; +import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; @@ -44,7 +45,6 @@ * and binds it to the model. */ public class ComboViewerComplex { - private final ComboViewer comboViewer; private final IObservableValue enabler = new WritableValue(); @@ -59,6 +59,7 @@ protected ComboViewerComplex( String labelValue, int comboSpan, List listeners) { + if (labelValue != null) { newLabel(parent, labelValue); } @@ -68,7 +69,7 @@ protected ComboViewerComplex( comboViewer.setInput(items); IViewerObservableValue viewerObservableValue = ViewerProperties.singleSelection().observe(comboViewer); - bindingContext.bindValue(viewerObservableValue, pojoObservableValue); + Binding binding = bindingContext.bindValue(viewerObservableValue, pojoObservableValue); enabler.setValue(true); ChainValidator validatorChain = new ChainValidator<>(viewerObservableValue, enabler, validators); @@ -83,6 +84,12 @@ protected ComboViewerComplex( for (ISelectionChangedListener listener : listeners) { comboViewer.addSelectionChangedListener(listener); } + comboViewer.getCombo().addDisposeListener(e -> { + bindingContext.removeBinding(binding); + bindingContext.removeValidationStatusProvider(validatorChain); + viewerObservableValue.dispose(); + validatorChain.dispose(); + }); } public ComboViewer getComboViewer() { @@ -94,6 +101,13 @@ public void setEnabled(boolean enabled) { enabler.setValue(enabled); } + public void selectItem(T item) { + Collection items = (Collection) comboViewer.getInput(); + if (item != null && items.contains(item)) { + comboViewer.setSelection(new StructuredSelection(item)); + } + } + public static ComboViewerComplexBuilder builder() { return new ComboViewerComplexBuilder<>(); } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/TextComplex.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/TextComplex.java index 561a1320..93c713bd 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/TextComplex.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/core/widget/TextComplex.java @@ -17,22 +17,24 @@ import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newControlDecoration; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLabel; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText; -import static com.amazonaws.util.ValidationUtils.assertNotNull; -import static com.amazonaws.util.ValidationUtils.assertStringNotEmpty; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.databinding.swt.ISWTObservableValue; -import org.eclipse.jface.databinding.swt.SWTObservables; +import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import com.amazonaws.eclipse.databinding.ChainValidator; @@ -42,12 +44,9 @@ * A complex Text widget including a Label, DataBinding, Validator and Decoration. */ public class TextComplex { - private final Text text; - private ControlDecoration controlDecoration; - private ISWTObservableValue swtObservableValue; + private final IObservableValue enabler = new WritableValue(); - private final ChainValidator validatorChain; private TextComplex( Composite composite, @@ -62,22 +61,34 @@ private TextComplex( int labelColSpan, String textMessage) { - if (createLabel) newLabel(composite, labelValue, labelColSpan); - text = newText(composite, "", textColSpan); + if (createLabel) { + Label label = newLabel(composite, labelValue, labelColSpan); + label.setToolTipText(textMessage); + } + text = newText(composite, defaultValue, textColSpan); + text.setToolTipText(textMessage); text.setMessage(textMessage); - controlDecoration = newControlDecoration(text, ""); - - swtObservableValue = SWTObservables.observeText(text, SWT.Modify); - dataBindingContext.bindValue(swtObservableValue, pojoObservableValue); + ControlDecoration controlDecoration = newControlDecoration(text, ""); + ISWTObservableValue swtObservableValue = WidgetProperties.text(SWT.Modify).observe(text); + Binding binding = dataBindingContext.bindValue(swtObservableValue, pojoObservableValue); enabler.setValue(true); - validatorChain = new ChainValidator<>( - swtObservableValue, enabler, validators); + ChainValidator validatorChain = new ChainValidator<>(swtObservableValue, enabler, validators); dataBindingContext.addValidationStatusProvider(validatorChain); - new DecorationChangeListener(controlDecoration, - validatorChain.getValidationStatus()); - if (modifyListener != null) text.addModifyListener(modifyListener); + new DecorationChangeListener(controlDecoration, validatorChain.getValidationStatus()); swtObservableValue.setValue(defaultValue); + + text.addDisposeListener(e -> { + dataBindingContext.removeBinding(binding); + dataBindingContext.removeValidationStatusProvider(validatorChain); + validatorChain.dispose(); + controlDecoration.dispose(); + swtObservableValue.dispose(); + }); + + if (modifyListener != null) { + text.addModifyListener(modifyListener); + } } // Whether enable widget or not. @@ -94,17 +105,21 @@ public Text getText() { return text; } - public static TextComplexBuilder builder() { - return new TextComplexBuilder(); + @NonNull + public static TextComplexBuilder builder( + @NonNull Composite parent, + @NonNull DataBindingContext dataBindingContext, + @NonNull IObservableValue pojoObservableValue) { + return new TextComplexBuilder(parent, dataBindingContext, pojoObservableValue); } public static class TextComplexBuilder { + private final Composite composite; + private final DataBindingContext dataBindingContext; + private final IObservableValue pojoObservableValue; + private final List validators = new ArrayList<>(); - private Composite composite; - private DataBindingContext dataBindingContext; - private IObservableValue pojoObservableValue; - private List validators = new ArrayList<>(); - private String labelValue; + private String labelValue = "Label: "; private String textMessage = ""; private ModifyListener modifyListener; @@ -113,29 +128,21 @@ public static class TextComplexBuilder { private int textColSpan = 1; private int labelColSpan = 1; - public TextComplex build() { - validateParameters(); + private TextComplexBuilder( + @NonNull Composite parent, + @NonNull DataBindingContext dataBindingContext, + @NonNull IObservableValue pojoObservableValue) { + this.composite = parent; + this.dataBindingContext = dataBindingContext; + this.pojoObservableValue = pojoObservableValue; + } + public TextComplex build() { return new TextComplex( composite, dataBindingContext, pojoObservableValue, validators, modifyListener, createLabel, labelValue, defaultValue, textColSpan, labelColSpan, textMessage); } - public TextComplexBuilder composite(Composite composite) { - this.composite = composite; - return this; - } - - public TextComplexBuilder dataBindingContext(DataBindingContext dataBindingContext) { - this.dataBindingContext = dataBindingContext; - return this; - } - - public TextComplexBuilder pojoObservableValue(IObservableValue pojoObservableValue) { - this.pojoObservableValue = pojoObservableValue; - return this; - } - public TextComplexBuilder addValidator(IValidator validator) { this.validators.add(validator); return this; @@ -162,7 +169,7 @@ public TextComplexBuilder labelValue(String labelValue) { } public TextComplexBuilder defaultValue(String defaultValue) { - this.defaultValue = defaultValue; + this.defaultValue = Optional.ofNullable(defaultValue).orElse(""); return this; } @@ -180,13 +187,5 @@ public TextComplexBuilder textMessage(String textMessage) { this.textMessage = textMessage; return this; } - - private void validateParameters() { - assertNotNull(composite, "Composite"); - assertNotNull(dataBindingContext, "DataBindingContext"); - assertNotNull(pojoObservableValue, "PojoObservableValue"); - if (createLabel) assertStringNotEmpty(labelValue, "LabelValue"); - } } - } diff --git a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/explorer/AwsAction.java b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/explorer/AwsAction.java index 9f000b67..bcb49355 100644 --- a/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/explorer/AwsAction.java +++ b/bundles/com.amazonaws.eclipse.core/src/com/amazonaws/eclipse/explorer/AwsAction.java @@ -76,18 +76,33 @@ public void run() { protected abstract void doRun(); - // Helper method to publish a filed action metric immediately + // Helper method to publish a failed action metric immediately public static void publishFailedAction(AwsToolkitMetricType metricType) { - new MetricsDataModel(metricType).addAttribute(END_RESULT, FAILED).publishEvent(); + publishFailedAction(new MetricsDataModel(metricType)); + } + + // Helper method to publish a failed action metric immediately + public static void publishFailedAction(MetricsDataModel dataModel) { + dataModel.addAttribute(END_RESULT, FAILED).publishEvent(); } // Helper method to publish a succeeded action metric immediately public static void publishSucceededAction(AwsToolkitMetricType metricType) { - new MetricsDataModel(metricType).addAttribute(END_RESULT, SUCCEEDED).publishEvent(); + publishSucceededAction(new MetricsDataModel(metricType)); + } + + // Helper method to publish a succeeded action metric immediately + public static void publishSucceededAction(MetricsDataModel dataModel) { + dataModel.addAttribute(END_RESULT, SUCCEEDED).publishEvent(); } // Helper method to publish a performed action metric immediately public static void publishPerformedAction(AwsToolkitMetricType metricType) { - new MetricsDataModel(metricType).addAttribute(END_RESULT, PERFORMED).publishEvent(); + publishPerformedAction(new MetricsDataModel(metricType)); + } + + // Helper method to publish a performed action metric immediately + public static void publishPerformedAction(MetricsDataModel dataModel) { + dataModel.addAttribute(END_RESULT, PERFORMED).publishEvent(); } } diff --git a/bundles/com.amazonaws.eclipse.dynamodb/src/com/amazonaws/eclipse/dynamodb/testtool/StartTestToolConfigurationWizardPage.java b/bundles/com.amazonaws.eclipse.dynamodb/src/com/amazonaws/eclipse/dynamodb/testtool/StartTestToolConfigurationWizardPage.java index 53d50e21..e7fb76c1 100644 --- a/bundles/com.amazonaws.eclipse.dynamodb/src/com/amazonaws/eclipse/dynamodb/testtool/StartTestToolConfigurationWizardPage.java +++ b/bundles/com.amazonaws.eclipse.dynamodb/src/com/amazonaws/eclipse/dynamodb/testtool/StartTestToolConfigurationWizardPage.java @@ -35,6 +35,7 @@ import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.ui.wizards.ErrorDecorator; +import com.amazonaws.eclipse.core.validator.IntegerRangeValidator; import com.amazonaws.eclipse.databinding.ChainValidator; import com.amazonaws.eclipse.dynamodb.DynamoDBPlugin; import com.amazonaws.eclipse.dynamodb.preferences.TestToolPreferencePage; @@ -149,6 +150,7 @@ public void handleChange(final ChangeEvent event) { /** * A validator that ensures the input is a valid port number. + * @deprecated for {@link IntegerRangeValidator} */ private static class PortValidator implements IValidator { @Override diff --git a/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/PlatformUtils.java b/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/PlatformUtils.java index 18603a5d..ff6b5dcc 100644 --- a/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/PlatformUtils.java +++ b/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/PlatformUtils.java @@ -16,6 +16,10 @@ package com.amazonaws.eclipse.ec2; import java.io.File; +import static com.amazonaws.eclipse.core.util.OsPlatformUtils.isWindows; +import static com.amazonaws.eclipse.core.util.OsPlatformUtils.isMac; +import static com.amazonaws.eclipse.core.util.OsPlatformUtils.isLinux; + import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -38,63 +42,6 @@ public class PlatformUtils { private static final String PPK_CONVERTER_EXE = "/lib/PemToPPKConverter.exe"; private static final Logger logger = Logger.getLogger(PlatformUtils.class.getName()); - /** - * Returns true if the current platform is a windows platform. - * - * @return True if the current platform is a windows platform. - */ - public boolean isWindows() { - String platform = System.getProperty("os.name"); - - if (platform == null) { - Status status = new Status(IStatus.WARNING, Ec2Plugin.PLUGIN_ID, - "No system property for 'os.name'"); - StatusManager.getManager().handle(status, StatusManager.LOG); - - return false; - } - - return platform.toLowerCase().contains("windows"); - } - - /** - * Returns true if the current platform is a Linux platform. - * - * @return True if the current platform is a Linux platform. - */ - public boolean isLinux() { - String platform = System.getProperty("os.name"); - - if (platform == null) { - Status status = new Status(IStatus.WARNING, Ec2Plugin.PLUGIN_ID, - "No system property for 'os.name'"); - StatusManager.getManager().handle(status, StatusManager.LOG); - - return false; - } - - return platform.toLowerCase().contains("linux"); - } - - /** - * Returns true if the current platform is a Mac platform. - * - * @return True if the current platform is a Mac platform. - */ - public boolean isMac() { - String platform = System.getProperty("os.name"); - - if (platform == null) { - Status status = new Status(IStatus.WARNING, Ec2Plugin.PLUGIN_ID, - "No system property for 'os.name'"); - StatusManager.getManager().handle(status, StatusManager.LOG); - - return false; - } - - return platform.toLowerCase().contains("mac os"); - } - /** * Returns true if the platform specific SSH client is correctly configured * and ready to be used on this system. diff --git a/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/preferences/ExternalToolsPreferencePage.java b/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/preferences/ExternalToolsPreferencePage.java index 30c691ba..8fbe4cfb 100644 --- a/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/preferences/ExternalToolsPreferencePage.java +++ b/bundles/com.amazonaws.eclipse.ec2/src/com/amazonaws/eclipse/ec2/preferences/ExternalToolsPreferencePage.java @@ -1,16 +1,16 @@ /* - * Copyright 2008-2012 Amazon Technologies, Inc. + * 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. + * 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. + * 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.preferences; @@ -29,6 +29,7 @@ import org.eclipse.ui.IWorkbenchPreferencePage; import com.amazonaws.eclipse.core.ui.WebLinkListener; +import com.amazonaws.eclipse.core.util.OsPlatformUtils; import com.amazonaws.eclipse.ec2.Ec2Plugin; import com.amazonaws.eclipse.ec2.PlatformUtils; @@ -44,9 +45,9 @@ public class ExternalToolsPreferencePage private StringFieldEditor sshOptions; private FileFieldEditor puttyExecutable; private StringFieldEditor sshUser; - - private static final int MAX_FIELD_EDITOR_COLUMNS = 3; - + + private static final int MAX_FIELD_EDITOR_COLUMNS = 3; + public ExternalToolsPreferencePage() { super("External Tool Configuration"); setPreferenceStore(Ec2Plugin.getDefault().getPreferenceStore()); @@ -62,20 +63,19 @@ public void init(IWorkbench workbench) {} @Override protected Control createContents(Composite parent) { Composite top = new Composite(parent, SWT.LEFT); - - // Sets the layout data for the top composite's + + // Sets the layout data for the top composite's // place in its parent's layout. top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); top.setLayout(new GridLayout()); - + Group sshClientGroup = new Group(top, SWT.LEFT); sshClientGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); sshClientGroup.setText("SSH Client:"); - - PlatformUtils platformUtils = new PlatformUtils(); - if (platformUtils.isWindows()) { + + if (OsPlatformUtils.isWindows()) { String puttyUrl = "http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html"; - + WebLinkListener webLinkListener = new WebLinkListener(); Link l = new Link(sshClientGroup, SWT.NONE); l.setText("PuTTY is required for remote shell connections " @@ -85,15 +85,15 @@ protected Control createContents(Composite parent) { GridData data = new GridData(GridData.FILL_HORIZONTAL); data.horizontalSpan = MAX_FIELD_EDITOR_COLUMNS; l.setLayoutData(data); - + puttyExecutable = newFileFieldEditor(PreferenceConstants.P_PUTTY_EXECUTABLE, "PuTTY Executable:", sshClientGroup); } else { // For the Mac, we use a custom AppleScript script that we ship with the // plugin so we don't need a seperate terminal exectuable. - if (!platformUtils.isMac()) { + if (!OsPlatformUtils.isMac()) { terminalExecutable = newFileFieldEditor(PreferenceConstants.P_TERMINAL_EXECUTABLE, "Terminal:", sshClientGroup); } - + sshClient = newFileFieldEditor(PreferenceConstants.P_SSH_CLIENT, "SSH Client:", sshClientGroup); sshOptions = new StringFieldEditor(PreferenceConstants.P_SSH_OPTIONS, "SSH Options: ", sshClientGroup); @@ -102,19 +102,19 @@ protected Control createContents(Composite parent) { sshOptions.load(); sshOptions.fillIntoGrid(sshClientGroup, MAX_FIELD_EDITOR_COLUMNS); } - + sshUser = new StringFieldEditor(PreferenceConstants.P_SSH_USER, "SSH User: ", sshClientGroup); sshUser.setPage(this); sshUser.setPreferenceStore(this.getPreferenceStore()); sshUser.load(); sshUser.fillIntoGrid(sshClientGroup, MAX_FIELD_EDITOR_COLUMNS); - + // Reset the layout to three columns after the FieldEditors have mucked with it GridLayout layout = (GridLayout)(sshClientGroup.getLayout()); layout.numColumns = MAX_FIELD_EDITOR_COLUMNS; layout.marginWidth = 10; layout.marginHeight = 8; - + return top; } @@ -127,9 +127,9 @@ protected void performDefaults() { if (sshClient != null) sshClient.loadDefault(); if (sshOptions != null) sshOptions.loadDefault(); if (sshUser != null) sshUser.loadDefault(); - + if (puttyExecutable != null) puttyExecutable.loadDefault(); - + super.performDefaults(); } @@ -141,10 +141,10 @@ public boolean performOk() { if (terminalExecutable != null) terminalExecutable.store(); if (sshClient != null) sshClient.store(); if (sshOptions != null) sshOptions.store(); - if (sshUser != null) sshUser.store(); + if (sshUser != null) sshUser.store(); if (puttyExecutable != null) puttyExecutable.store(); - + return super.performOk(); } @@ -154,14 +154,14 @@ public boolean performOk() { /** * Convenience method for creating a FileFieldEditor. - * + * * @param preferenceName * The preference managed by this FileFieldEditor. * @param label * The label for this FieldEditor. * @param parent * The parent for this new FieldEditor widget. - * + * * @return The new FileFieldEditor. */ private FileFieldEditor newFileFieldEditor(String preferenceName, String label, Composite parent) { diff --git a/bundles/com.amazonaws.eclipse.elasticbeanstalk/src/com/amazonaws/eclipse/elasticbeanstalk/server/ui/databinding/MinMaxLengthValidator.java b/bundles/com.amazonaws.eclipse.elasticbeanstalk/src/com/amazonaws/eclipse/elasticbeanstalk/server/ui/databinding/MinMaxLengthValidator.java index ef3272c7..0fb9e404 100644 --- a/bundles/com.amazonaws.eclipse.elasticbeanstalk/src/com/amazonaws/eclipse/elasticbeanstalk/server/ui/databinding/MinMaxLengthValidator.java +++ b/bundles/com.amazonaws.eclipse.elasticbeanstalk/src/com/amazonaws/eclipse/elasticbeanstalk/server/ui/databinding/MinMaxLengthValidator.java @@ -18,10 +18,14 @@ import org.eclipse.core.databinding.validation.ValidationStatus; import org.eclipse.core.runtime.IStatus; +import com.amazonaws.eclipse.core.validator.StringLengthValidator; + /** * IValidator implementation that tests that a string for length constraints. * Excludes the range specified. + * @deprecated to {@link StringLengthValidator} */ +@Deprecated public class MinMaxLengthValidator implements IValidator { private final String message = "%s must be %d to %d characters in length."; private final int min; diff --git a/bundles/com.amazonaws.eclipse.lambda/META-INF/MANIFEST.MF b/bundles/com.amazonaws.eclipse.lambda/META-INF/MANIFEST.MF index 1d3e6847..45257776 100644 --- a/bundles/com.amazonaws.eclipse.lambda/META-INF/MANIFEST.MF +++ b/bundles/com.amazonaws.eclipse.lambda/META-INF/MANIFEST.MF @@ -34,10 +34,13 @@ Require-Bundle: org.eclipse.ui, org.eclipse.m2e.core.ui;bundle-version="1.5.1", org.eclipse.m2e.maven.runtime;bundle-version="1.5.1", org.eclipse.m2e.archetype.common;bundle-version="1.5.1", - com.amazonaws.eclipse.javasdk;bundle-version="1.11.130" + com.amazonaws.eclipse.javasdk;bundle-version="1.11.130", + org.eclipse.jdt.debug.ui, + org.eclipse.jdt.annotation Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Export-Package: com.amazonaws.eclipse.lambda.blueprint, + com.amazonaws.eclipse.lambda.launching, com.amazonaws.eclipse.lambda.project.template, com.amazonaws.eclipse.lambda.project.template.data, com.amazonaws.eclipse.lambda.project.wizard.model, @@ -46,4 +49,9 @@ Export-Package: com.amazonaws.eclipse.lambda.blueprint, com.amazonaws.eclipse.lambda.serverless.model.transform, com.amazonaws.eclipse.lambda.serverless.template, freemarker.template -Import-Package: org.eclipse.ui.navigator +Import-Package: org.eclipse.core.variables, + org.eclipse.debug.internal.ui, + org.eclipse.debug.ui, + org.eclipse.debug.ui.console, + org.eclipse.m2e.actions, + org.eclipse.ui.navigator diff --git a/bundles/com.amazonaws.eclipse.lambda/icons/sam-local.png b/bundles/com.amazonaws.eclipse.lambda/icons/sam-local.png new file mode 100644 index 0000000000000000000000000000000000000000..7f4c7cb58dcc395006cd40e63af3608792fa16bf GIT binary patch literal 1589 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRv!3HG#?eZ=GDaPU;cPEB*=VV?2Ih+L^k;M!Q z+`=Ht$S`Y;1W-X_W=KRygs+cPa(=E}VoH8es$NBI0Z=sqgH44MkeQoWlBiITo0C^; zRbi_HR$&EXgM{^!6u?SKvTcjnX^rOvA)@nC0;z8o*VC&T)8ye&ZFDm<+V$9#qvi& zl~|M3Vr?NoY<>*`#+vs>3{0= zL4A2+WqnoPloAw4WZ^Ch} z+Wh^k=I?Llt_oe{5bCk+oYVJ%(-r3BtvVUwQJ`Hn*~}q}bx&2bBR8{o+K-vR?-x1G z^_(%Qzk%axLnDicitmdoRfW~MAAa{XG3|cvYs(5lE7lShQD^Sx&7GT9t_Yp3on#hT zQn1O6@nec@NC1o4XEqD7i7`zRf2;G0DVm$MX&A^!NIYo&{{E+h(+fjm5#jQpBMYXc za2`Oh*4fYxSHWSs1Uq4e{-!FIlp6J0Peg{LYmnFs?&RP-V`JAh}!vA^I zjZJSWPaI)g^2B6IZk1oz-VLnox{UQ3p55EeaE1NYjT;eddp6ZyZ_H8qn{$-^$1&fv z&)S}?(a;lX2~#O31DyVB3E%)(dd+LCQoU1cgF zA|{+qF;4pZ&2+|DKjzfahdkU04+sb3>o*2WdGX>*LqXvEwHrHMx5Y#)T(m;y__pM( zwG(_?j(%9XTjg0t=d)v>9uk{&zGz->!fl(5^6`x`X1hCAPWd5cY~I8Xr1QK(g5&(F zWs1`{<94!$@BjTad-?Oja(z9|4(NKSv2HqBReS$i-<0B=w#mJb79NRfJWfg^9DAB@ zlzT~{!0MoR%iqerbq}8Lt}5V*f?)TfH>*TW1s~}UZq(4)E2SQ!b^Mg2`zp2@J1ni@ zZd|*krgLO>xY;(T*SD>&+kEr2Iq$Gcr=?`KZgpr&h3Hl{#l=y_q2&zQgazR=>tN7H$lf4O=5Vk@@Yb55(hW%mLj1@*0)9yP9feye$rxU;kK zlHI#!_wv + + + - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + + + + + + + + + + + - - - - + + - - - - + + - - + + + + + + + + @@ -253,4 +304,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/LambdaPlugin.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/LambdaPlugin.java index 4edcffef..51bc0419 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/LambdaPlugin.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/LambdaPlugin.java @@ -33,12 +33,14 @@ public class LambdaPlugin extends AbstractAwsPlugin { public static final String DEFAULT_REGION = "us-east-1"; public static final String IMAGE_LAMBDA = "lambda-service"; public static final String IMAGE_FUNCTION = "function"; + public static final String IMAGE_SAM_LOCAL = "sam-local"; private static final Map IMAGE_REGISTRY_MAP = new HashMap<>(); static { IMAGE_REGISTRY_MAP.put(IMAGE_LAMBDA, "/icons/lambda-service.png"); IMAGE_REGISTRY_MAP.put(IMAGE_FUNCTION, "/icons/function.png"); + IMAGE_REGISTRY_MAP.put(IMAGE_SAM_LOCAL, "/icons/sam-local.png"); } /* @@ -57,12 +59,11 @@ public class LambdaPlugin extends AbstractAwsPlugin { @Override public void start(BundleContext context) throws Exception { super.start(context); + plugin = this; initializePreferenceStoreDefaults(); projectChangeTracker.clearDirtyFlags(); projectChangeTracker.start(); - - plugin = this; } /** diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/dialog/SamLocalGenerateEventDialog.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/dialog/SamLocalGenerateEventDialog.java new file mode 100644 index 00000000..fc5f178a --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/dialog/SamLocalGenerateEventDialog.java @@ -0,0 +1,281 @@ +/* + * Copyright 2017 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.dialog; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.databinding.beans.PojoProperties; +import org.eclipse.core.databinding.observable.Observables; +import org.eclipse.core.databinding.observable.map.WritableMap; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.SaveAsDialog; + +import com.amazonaws.eclipse.core.mobileanalytics.AwsToolkitMetricType; +import com.amazonaws.eclipse.core.mobileanalytics.MetricsDataModel; +import com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory; +import com.amazonaws.eclipse.core.util.CliUtil; +import com.amazonaws.eclipse.core.util.CliUtil.CliProcessTracker; +import com.amazonaws.eclipse.core.widget.ComboViewerComplex; +import com.amazonaws.eclipse.core.widget.TextComplex; +import com.amazonaws.eclipse.explorer.AwsAction; +import com.amazonaws.eclipse.lambda.LambdaPlugin; +import com.amazonaws.eclipse.lambda.launching.SamLocalConstants; +import com.amazonaws.eclipse.lambda.project.wizard.util.FunctionProjectUtil; + +/** + * Dialog for generating Lambda handler event json file using SAM Local. + */ +public class SamLocalGenerateEventDialog extends TitleAreaDialog { + private final SamLocalLambdaEventDataModel dataModel = new SamLocalLambdaEventDataModel(); + private final DataBindingContext bindingContext; + + private ScrolledComposite scrolledComposite; + private Composite composite; + + private ComboViewerComplex lambdaEventCombo; + + public SamLocalGenerateEventDialog(Shell parentShell) { + super(parentShell); + this.bindingContext = new DataBindingContext(); + } + + public SamLocalLambdaEventDataModel getDataModel() { + return dataModel; + } + + @Override + public void create() { + super.create(); + setTitle("Generate Lambda event template"); + setMessage("Generates Lambda events (e.g. for S3/Kinesis etc) that can be used as the input of the Lambda function."); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite rootComposite = (Composite) super.createDialogArea(parent); + + lambdaEventCombo = ComboViewerComplex.builder() + .composite(WizardWidgetFactory.newComposite(rootComposite, 1, 2)) + .labelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof SamLocalLambdaEvent) { + return ((SamLocalLambdaEvent) element).presentation; + } + return super.getText(element); + } + }) + .pojoObservableValue(PojoProperties.value(SamLocalLambdaEventDataModel.P_EVENT_TYPE).observe(dataModel)) + .bindingContext(bindingContext) + .items(Arrays.asList(SamLocalLambdaEvent.values())) + .defaultItem(SamLocalLambdaEvent.S3) + .labelValue("Select event type: ") + .addListeners(e -> onLambdaEventComboSelect()) + .build(); + + Group parameterListGroup = WizardWidgetFactory.newGroup(rootComposite, "Parameter List"); + + scrolledComposite = new ScrolledComposite(parameterListGroup, SWT.V_SCROLL); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.setExpandVertical(true); + GridDataFactory.fillDefaults().grab(true, true).applyTo(scrolledComposite); + + composite = new Composite(scrolledComposite, SWT.None); + GridDataFactory.fillDefaults().grab(true, true).applyTo(composite); + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite); + scrolledComposite.setContent(composite); + + onLambdaEventComboSelect(); + + return rootComposite; + } + + private void onLambdaEventComboSelect() { + for (Control control : composite.getChildren()) { + control.dispose(); + } + + SamLocalLambdaEvent eventType = dataModel.getEventType(); + for (SamLocalEventParameter parameter : eventType.parameterList) { + TextComplex.builder(composite, bindingContext, Observables.observeMapEntry(dataModel.getParameterList(), parameter.name, String.class)) + .defaultValue(parameter.defaultValue) + .labelValue(parameter.presentation) + .textMessage(parameter.description) + .build(); + } + setMessage(eventType.description); + + Shell shell = composite.getShell(); + shell.setMinimumSize(getInitialSize()); + shell.layout(true, true); + } + + @Override + protected void okPressed() { + SaveAsDialog saveAsDialog = new SaveAsDialog(this.getShell()); + if (saveAsDialog.open() == Window.OK) { + dataModel.setResultPath(saveAsDialog.getResult()); + generateEventFile(); + } + super.okPressed(); + } + + @Override + protected boolean isResizable() { + return true; + } + + private void generateEventFile() { + SamLocalLambdaEvent eventType = dataModel.getEventType(); + List builder = new ArrayList<>(); + builder.add(LambdaPlugin.getDefault().getPreferenceStore().getString(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE)); + builder.add("local"); + builder.add("generate-event"); + builder.add(eventType.commandName); + + for (SamLocalEventParameter parameter : eventType.parameterList) { + String value = (String) dataModel.parameterList.get(parameter.name); + if (value == null || value.isEmpty()) { + value = parameter.defaultValue; + } + builder.add("--" + parameter.name); + builder.add(value.replace("\"", "\\\"")); + } + + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(dataModel.resultPath.segment(0)); + File outputFile = project.getLocation().append(dataModel.resultPath.removeFirstSegments(1)).toFile(); + MetricsDataModel metricsDataModel = new MetricsDataModel(AwsToolkitMetricType.SAMLOCAL_GENERATE_EVENT); + metricsDataModel.addAttribute("EventType", eventType.presentation); + try { + ByteArrayOutputStream stdErrOutput = new ByteArrayOutputStream(); + CliProcessTracker tracker = CliUtil.executeCommand(builder, Collections.emptyMap(), + new FileOutputStream(outputFile), stdErrOutput); + int exitValue = tracker.waitForStream(); + FunctionProjectUtil.refreshProject(project); + + String stdErr = stdErrOutput.toString(); + if (exitValue != 0 && stdErr != null && !stdErr.isEmpty()) { + LambdaPlugin.getDefault().reportException(stdErr, null); + AwsAction.publishFailedAction(metricsDataModel); + } else { + AwsAction.publishSucceededAction(metricsDataModel); + } + } catch (IOException e1) { + LambdaPlugin.getDefault().reportException("Failed to generate event json file.", e1); + AwsAction.publishFailedAction(metricsDataModel); + } + } + + public static class SamLocalLambdaEventDataModel { + public static final String P_EVENT_TYPE = "eventType"; + + private SamLocalLambdaEvent eventType; + private WritableMap parameterList = new WritableMap(String.class, String.class); + private IPath resultPath; + + public IPath getResultPath() { + return resultPath; + } + + public void setResultPath(IPath resultPath) { + this.resultPath = resultPath; + } + + public SamLocalLambdaEvent getEventType() { + return eventType; + } + + public void setEventType(SamLocalLambdaEvent eventType) { + this.eventType = eventType; + } + + public WritableMap getParameterList() { + return parameterList; + } + } + + public static enum SamLocalLambdaEvent { + S3("s3", "S3 Event", "Generates a sample Amazon S3 event", + new SamLocalEventParameter("region", "Region", "The region the event should come from (default: \"us-east-1\")", "us-east-1"), + new SamLocalEventParameter("bucket", "Bucket", "The S3 bucket the event should reference (default: \"example-bucket\")", "example-bucket"), + new SamLocalEventParameter("key", "Key", "The S3 key the event should reference (default: \"test/key\")", "test/key")), + SNS("sns", "SNS Event", "Generates a sample Amazon SNS event", + new SamLocalEventParameter("message", "Message", "The SNS message body (default: \"example message\")", "example message"), + new SamLocalEventParameter("topic", "Topic", "The SNS topic (default: \"arn:aws:sns:us-east-1:111122223333:ExampleTopic\")", "arn:aws:sns:us-east-1:111122223333:ExampleTopic"), + new SamLocalEventParameter("subject", "Subject", "The SNS subject (default: \"example subject\")", "example subject")), + KINESIS("kinesis", "Kinesis Event", "Generates a sample Amazon Kinesis event", + new SamLocalEventParameter("region", "AWS Region", "The region the event should come from (default: \"us-east-1\")", "us-east-1"), + new SamLocalEventParameter("partition", "Partition", "The Kinesis partition key (default: \"partitionKey-03\")", "partitionKey-03"), + new SamLocalEventParameter("sequence", "Sequence", "The Kinesis sequence number (default: \"49545115243490985018280067714973144582180062593244200961\")", "49545115243490985018280067714973144582180062593244200961"), + new SamLocalEventParameter("data", "Data", "The Kinesis message payload, with no base64 encoded (default: \"Hello, this is a test 123.\")", "Hello, this is a test 123.")), + DYNAMODB("dynamodb", "DynamoDB Event", "Generates a sample Amazon DynamoDB event", + new SamLocalEventParameter("region", "Region", "The region the event should come from (default: \"us-east-1\")", "us-east-1")), + API("api", "API Gateway Event", "Generates a sample Amazon API Gateway event", + new SamLocalEventParameter("method", "Method", "HTTP method (default: \"POST\")", "POST"), + new SamLocalEventParameter("body", "Body", "HTTP body (default: \"{ \"test\": \"body\"}\")", "{ \"test\": \"body\"}"), + new SamLocalEventParameter("resource", "Resource", "API Gateway resource name (default: \"/{proxy+}\")", "/{proxy+}"), + new SamLocalEventParameter("path", "Path", "HTTP path (default: \"/examplepath\")", "/examplepath")), + SCHEDULE("schedule", "Scheduled Event", "Generates a sample scheduled event", + new SamLocalEventParameter("region", "Region", "The region the event should come from (default: \"us-east-1\")", "us-east-1")), + ; + + private final String commandName; + private final String presentation; + private final String description; + private final SamLocalEventParameter[] parameterList; + + private SamLocalLambdaEvent(String commandName, String presentation, String description, SamLocalEventParameter... eventParameters) { + this.commandName = commandName; + this.presentation = presentation; + this.description = description; + parameterList = eventParameters; + } + } + + public static class SamLocalEventParameter { + private final String name; + private final String presentation; + private final String description; + private final String defaultValue; + + public SamLocalEventParameter(String name, String presentation, String description, String defaultValue) { + this.name = name; + this.presentation = presentation; + this.description = description; + this.defaultValue = defaultValue; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/invoke/handler/InvokeFunctionHandler.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/invoke/handler/InvokeFunctionHandler.java index 11d805af..47006faa 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/invoke/handler/InvokeFunctionHandler.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/invoke/handler/InvokeFunctionHandler.java @@ -32,12 +32,11 @@ import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.console.ConsolePlugin; -import org.eclipse.ui.console.IConsole; -import org.eclipse.ui.console.IConsoleManager; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.console.MessageConsoleStream; import com.amazonaws.eclipse.core.AwsToolkitCore; +import com.amazonaws.eclipse.core.util.PluginUtils; import com.amazonaws.eclipse.lambda.LambdaAnalytics; import com.amazonaws.eclipse.lambda.LambdaPlugin; import com.amazonaws.eclipse.lambda.invoke.logs.CloudWatchLogsUtils; @@ -139,7 +138,7 @@ private static void _doInvoke(final IProject project, String handlerToBeInvoked = metadata.getLastInvokeHandler(); - MessageConsole lambdaConsole = getOrCreateLambdaConsoleIfNotExist( + MessageConsole lambdaConsole = PluginUtils.getOrCreateMessageConsole( handlerToBeInvoked + " Lambda Console"); lambdaConsole.clearConsole(); ConsolePlugin.getDefault().getConsoleManager().showConsoleView(lambdaConsole); @@ -233,27 +232,6 @@ private static void showLambdaLiveLog(InvokeResult result, MessageConsoleStream } } - private static MessageConsole getOrCreateLambdaConsoleIfNotExist(String consoleName) { - IConsoleManager consoleManager = ConsolePlugin.getDefault() - .getConsoleManager(); - - // Search existing consoles - if (consoleManager.getConsoles() != null) { - for (IConsole console : consoleManager.getConsoles()) { - if (consoleName.equals(console.getName()) - && (console instanceof MessageConsole)) { - return (MessageConsole)console; - } - } - } - - // If not found, create a new console - MessageConsole newConsole = new MessageConsole(consoleName, null); - ConsolePlugin.getDefault().getConsoleManager() - .addConsoles(new IConsole[] { newConsole }); - return newConsole; - } - private static void updateFunctionCode(AWSLambda lambda, IProject project, String funcName, String bucketName, MessageConsoleStream out) throws IOException { diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleColorProvider.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleColorProvider.java new file mode 100644 index 00000000..09af525f --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleColorProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 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.launching; + +import org.eclipse.debug.ui.console.ConsoleColorProvider; +import org.eclipse.swt.graphics.Color; + +import com.amazonaws.eclipse.lambda.ui.LambdaPluginColors; + +/** + * Color for SAM Local terminal output. The default color is red as SAM Local's output + * goes to stderr even they are not actually error messages. We override it to be black. + */ +public class SamLocalConsoleColorProvider extends ConsoleColorProvider { + + @Override + public Color getColor(String streamIdentifer) { + // Make the output black. + return LambdaPluginColors.BLACK; + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTracker.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTracker.java new file mode 100644 index 00000000..4702e7a2 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTracker.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 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.launching; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.debug.ui.console.IConsole; +import org.eclipse.debug.ui.console.IConsoleLineTracker; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IRegion; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.console.IHyperlink; + +import com.amazonaws.eclipse.core.util.WorkbenchUtils; +import com.amazonaws.eclipse.lambda.LambdaPlugin; + +/** + * Recognize the console URL and convert it as a link to be clickable. + */ +public class SamLocalConsoleLineTracker implements IConsoleLineTracker { + // The pattern accept a four-digit IP address or localhost as a special IP address, with a port number at the end. + private static final String URL_LINK_PATTERN_STRING = "((http|https)://)?" + + "(((\\d|\\d{2}|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d|\\d{2}|1\\d{2}|2[0-4]\\d|25[0-5])|localhost)" + + ":(6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5]|[0-5]?\\d{1,5})"; + + public static final Pattern URL_PATTERN = Pattern.compile(URL_LINK_PATTERN_STRING); + + private IConsole console; + + @Override + public void init(IConsole console) { + this.console = console; + } + + @Override + public void lineAppended(IRegion line) { + try { + int offset = line.getOffset(); + int length = line.getLength(); + String text = console.getDocument().get(offset, length); + Matcher matcher = URL_PATTERN.matcher(text); + + String url = null; + if (matcher.find()) { + url = matcher.group(); + offset += matcher.start(); + } + + if (url != null) { + final String thisUrl = url; + console.addLink(new IHyperlink() { + + @Override + public void linkExited() {} + + @Override + public void linkEntered() {} + + @Override + public void linkActivated() { + try { + WorkbenchUtils.openInternalBrowserAsEditor(new URL(thisUrl), PlatformUI.getWorkbench()); + } catch (MalformedURLException e) { + LambdaPlugin.getDefault().logError("Cannot open the URL: " + thisUrl, e); + } + } + }, offset, url.length()); + } + } catch (BadLocationException e) { + LambdaPlugin.getDefault().logError("Failed to process the console document.", e); + } + } + + @Override + public void dispose() { + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConstants.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConstants.java new file mode 100644 index 00000000..dba499b7 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalConstants.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 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.launching; + +/** + * Constants used in SAM Local feature, mostly are attribute constants of SAM Local Launch configurations. + */ +public class SamLocalConstants { + + // Preference name for AWS SAM Local executable + public static String P_SAM_LOCAL_EXECUTABLE = "AwsSamLocalExecutable"; + + // SAM Local process type. This is configured in plugin.xml + public static String PROCESS_TYPE = "sam_local"; + + // Attribute names for executing a SAM Local command. + public static String A_SAM = "sam"; + public static String A_MAVEN_GOALS = "maven-goals"; + public static String A_ACTION = "action"; + public static String A_PROJECT = "project"; + + // These attribute names must be identical to those of the command line parameter names + public static String A_PROFILE = "profile"; + public static String A_TEMPLATE = "template"; + public static String A_ENV_VARS = "env-vars"; + public static String A_DEBUG_PORT = "debug-port"; + public static String A_REGION = "region"; + public static String A_CODE_URI = "code-uri"; + public static String A_TIME_OUT = "timeout"; + + // Attributes for `sam local invoke` only + public static String A_LAMBDA_IDENTIFIER = "lambda-id"; + public static String A_EVENT = "event"; + + // Attributes for `sam local start-api` only + public static String A_PORT = "port"; + public static String A_HOST = "host"; + + // Doc links + public static String LINKS_INSTALL_SAM_LOCAL = "https://github.com/awslabs/aws-sam-local#installation"; + public static String LINKS_SAM_LOCAL_ANNOUNCEMENT = "https://aws.amazon.com/about-aws/whats-new/2017/08/introducing-aws-sam-local-a-cli-tool-to-test-aws-lambda-functions-locally/"; +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalDelegate.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalDelegate.java new file mode 100644 index 00000000..086321ee --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalDelegate.java @@ -0,0 +1,300 @@ +/* + * Copyright 2017 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.launching; + +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_ACTION; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_CODE_URI; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_DEBUG_PORT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_ENV_VARS; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_EVENT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_HOST; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_LAMBDA_IDENTIFIER; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_MAVEN_GOALS; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PORT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PROFILE; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PROJECT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_REGION; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_SAM; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_TEMPLATE; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_TIME_OUT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.PROCESS_TYPE; + +import java.io.File; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.model.ILaunchConfigurationDelegate; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.ui.console.IOConsole; +import org.eclipse.ui.console.IOConsoleOutputStream; + +import com.amazonaws.eclipse.core.mobileanalytics.AwsToolkitMetricType; +import com.amazonaws.eclipse.core.mobileanalytics.MetricsDataModel; +import com.amazonaws.eclipse.core.util.CliUtil; +import com.amazonaws.eclipse.core.util.MavenBuildLauncher; +import com.amazonaws.eclipse.core.util.PluginUtils; +import com.amazonaws.eclipse.core.util.RemoteDebugLauncher; +import com.amazonaws.eclipse.explorer.AwsAction; +import com.amazonaws.eclipse.lambda.LambdaPlugin; +import com.amazonaws.eclipse.lambda.launching.SamLocalExecution.LaunchMode; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel.SamAction; +import com.amazonaws.eclipse.lambda.serverless.Serverless; +import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessModel; +import com.amazonaws.eclipse.lambda.ui.LambdaPluginColors; + +public class SamLocalDelegate implements ILaunchConfigurationDelegate { + + @Override + public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) + throws CoreException { + + MetricsDataModel metricsDataModel = new MetricsDataModel(AwsToolkitMetricType.SAMLOCAL_LAUNCH); + try { + LaunchMode launchMode = LaunchMode.fromValue(mode); + metricsDataModel.addAttribute("LaunchMode", launchMode.getMode()); + String projectName = configuration.getAttribute(A_PROJECT, (String) null); + + if (projectName == null || projectName.isEmpty()) { + throw new IllegalArgumentException("The project name must be provided!"); + } + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + SubMonitor subMonitor = SubMonitor.convert(monitor, 100); + + subMonitor.setTaskName("Running Maven build to generate the artifact..."); + long startTime = System.currentTimeMillis(); + new MavenBuildLauncher(project, configuration.getAttribute(A_MAVEN_GOALS, RunSamLocalDataModel.DEFAULT_MAVEN_GOALS), + subMonitor.newChild(30)).launch(); + long endTime = System.currentTimeMillis(); + metricsDataModel.addMetric("MavenBuildTimeMilli", (double)(endTime-startTime)); + subMonitor.worked(30); + + List commandLine = buildSamLocalCommandLine(launchMode, configuration.getAttributes()); + Map envvar = new HashMap<>(); + String regionValue = configuration.getAttribute(A_REGION, RunSamLocalDataModel.DEFAULT_REGION); + envvar.put("AWS_REGION", regionValue); + Process samLocalCliProcess = CliUtil.buildProcess(commandLine, envvar); + + Map attributes = new HashMap<>(); + attributes.put(IProcess.ATTR_PROCESS_TYPE, PROCESS_TYPE); + IProcess samLocalProcess = DebugPlugin.newProcess(launch, samLocalCliProcess, projectName, attributes); + + IOConsole samLocalConsole = (IOConsole) DebugUITools.getConsole(samLocalProcess); + IOConsoleOutputStream samLocalOutputStream = samLocalConsole.newOutputStream(); + samLocalOutputStream.setColor(LambdaPluginColors.GREY); + safeWriteToConsole(samLocalOutputStream, "Running command: " + commandLine.stream().collect(Collectors.joining(" "))); + + int debugPort = Integer.parseInt(configuration.getAttribute(A_DEBUG_PORT, String.valueOf(RunSamLocalDataModel.DEFAULT_DEBUG_PORT))); + SamAction command = SamAction.fromValue(configuration.getAttribute(A_ACTION, SamAction.INVOKE.getName())); + subMonitor.worked(20); + + metricsDataModel.addAttribute("SamLocalCommand", command.getName()); + if (command == SamAction.INVOKE) { + try { + waitAndLaunchDebugger(project, launchMode, debugPort, samLocalCliProcess, subMonitor, samLocalOutputStream, 50); + } catch (CoreException e) { + samLocalProcess.terminate(); + throw new RuntimeException("Failed to launch Eclipse Debugger", e); + } + } else if (command == SamAction.START_API) { + try { + int invokeTimes = 0; + while (!subMonitor.isCanceled() && !samLocalProcess.isTerminated()) { + waitAndLaunchDebugger(project, launchMode, debugPort, samLocalCliProcess, subMonitor, samLocalOutputStream, 1); + ++invokeTimes; + } + metricsDataModel.addMetric("InvokeTimes", (double)invokeTimes); + } catch (CoreException e) { + samLocalProcess.terminate(); + throw new RuntimeException("Failed to launch Eclipse Debugger", e); + } + } + if (subMonitor.isCanceled()) { + samLocalProcess.terminate(); + } + while (!samLocalProcess.isTerminated()) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + } + } + safeWriteToConsole(samLocalOutputStream, "SAM Local invocation done."); + subMonitor.done(); + AwsAction.publishSucceededAction(metricsDataModel); + } catch (Exception e) { + LambdaPlugin.getDefault().reportException("Failed to launch SAM Local.", e); + AwsAction.publishFailedAction(metricsDataModel); + } + } + + /** + * Wait the target port to be taken by the process and kick off Eclipse Debugger. + * + * @param project The target project the Debugger is running in + * @param mode debug or run mode + * @param debugPort The debug port + * @param samLocalCliProcess SAM Local process + * @param subMonitor {@link SubMonitor} for reporting the progress + * @param outputStream The console output stream for showing the current status + * @param totalWork Total work the Debugger takes + * @throws CoreException + */ + private void waitAndLaunchDebugger(IProject project, LaunchMode mode, int debugPort, + Process samLocalCliProcess, SubMonitor subMonitor, IOConsoleOutputStream outputStream, int totalWork) throws CoreException { + if (mode == LaunchMode.DEBUG) { + String statusUpdateMessage = "Waiting for SAM Local to attach the port " + debugPort; + subMonitor.setTaskName(statusUpdateMessage); + safeWriteToConsole(outputStream, statusUpdateMessage); + } + + if (waitForPortBeingTakenByProcess(samLocalCliProcess, debugPort, subMonitor)) { + String statusUpdateMessage = "Running Eclipse Debugger..."; + subMonitor.setTaskName(statusUpdateMessage); + safeWriteToConsole(outputStream, statusUpdateMessage); + new RemoteDebugLauncher(project, debugPort, subMonitor.newChild(totalWork)).launch(); + subMonitor.worked(totalWork); + } + } + + private void safeWriteToConsole(IOConsoleOutputStream outputStream, String content) { + try { + outputStream.write("[AWS Toolkit] " + content + "\n"); + } catch (IOException e) { + LambdaPlugin.getDefault().logError("Failed to write to SAM Local console.", e); + } + } + + /** + * Create a temporary template file based on the provided configuration map. It modifies the + * template file in A_TEMPLATE with the overriding value of A_CODE_URI and A_TIME_OUT. + */ + private String createTemporaryTemplateFile(Map config) throws CoreException { + String codeUri = (String) config.get(A_CODE_URI); + String timeOut = (String) config.get(A_TIME_OUT); + String project = (String) config.get(A_PROJECT); + String templateFile = (String) config.get(A_TEMPLATE); + + String templateFilePath = PluginUtils.variablePluginReplace(templateFile); + try { + ServerlessModel serverlessModel = Serverless.load(templateFilePath); + serverlessModel.getServerlessFunctions().forEach((k, v) -> { + if (codeUri != null && !codeUri.isEmpty()) { + v.setCodeUri(codeUri); + } + v.setTimeout(Integer.parseInt(timeOut)); + }); + + IPath tempTemplate = ResourcesPlugin.getWorkspace().getRoot().getProject(project).getLocation(); + + String outputPath = tempTemplate.append(".serverless.template").toOSString(); + File outputFile = Serverless.write(serverlessModel, outputPath); + outputFile.deleteOnExit(); + return outputPath; + } catch (IOException e ) { + // Any error occurs, fall back to the original template file. + return templateFilePath; + } + } + + private List buildSamLocalCommandLine(LaunchMode mode, Map config) throws CoreException { + final Set invokeParamSet = new HashSet<>(Arrays.asList(A_TEMPLATE, A_ENV_VARS, A_EVENT, A_PROFILE)); + final Set startApiParamSet = new HashSet<>(Arrays.asList(A_TEMPLATE, A_ENV_VARS, A_PROFILE, A_HOST, A_PORT)); + + if (mode == LaunchMode.DEBUG) { + invokeParamSet.add(A_DEBUG_PORT); + startApiParamSet.add(A_DEBUG_PORT); + } + + List commandLine = new ArrayList<>(); + commandLine.add(config.get(A_SAM).toString()); + commandLine.add("local"); + commandLine.add(config.get(A_ACTION).toString()); + + SamAction samAction = SamAction.fromValue(config.get(A_ACTION).toString()); + switch (samAction) { + case INVOKE: + if (config.get(A_LAMBDA_IDENTIFIER) != null) { + commandLine.add(config.get(A_LAMBDA_IDENTIFIER).toString()); + } + commandLine = buildCommandLineFromMap(commandLine, config, invokeParamSet); + break; + case START_API: + commandLine = buildCommandLineFromMap(commandLine, config, startApiParamSet); + break; + } + + return commandLine; + } + + private List buildCommandLineFromMap(List commandLine, Map parameters, Set parameterSet) + throws CoreException { + // Replacing these attributes with actually values from the path placeholder + Set replacableParams = new HashSet<>(Arrays.asList(A_TEMPLATE, A_EVENT, A_ENV_VARS)); + for (Entry entry : parameters.entrySet()) { + String key = entry.getKey(); + if (!parameterSet.contains(key)) { + continue; + } + String value = parameters.get(key).toString(); + if (replacableParams.contains(key)) { + value = PluginUtils.variablePluginReplace(value); + } + commandLine.add("--" + key); + if (key.equals(A_TEMPLATE)) { + value = createTemporaryTemplateFile(parameters); + } + commandLine.add(value); + } + return commandLine; + } + + // Wait for the port being take by the specified Process. + // Return true if the port is taken in the given condition, otherwise false. + private boolean waitForPortBeingTakenByProcess(Process process, int portNo, IProgressMonitor monitor) { + long pingIntervalMilli = 100; + while (process.isAlive() && !monitor.isCanceled()) { + try (Socket socket = new Socket("localhost", portNo)) { + return true; + } catch (UnknownHostException e1) { + return false; + } catch (IOException e1) {} + + try { + Thread.sleep(pingIntervalMilli); + } catch (InterruptedException e) {} + } + return false; + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalExecution.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalExecution.java new file mode 100644 index 00000000..2da4fdf1 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalExecution.java @@ -0,0 +1,184 @@ +/* + * Copyright 2017 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.launching; + +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PROJECT; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.debug.ui.IDebugModelPresentation; +import org.eclipse.debug.ui.ILaunchGroup; +import org.eclipse.debug.ui.ILaunchShortcut; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; + +import com.amazonaws.eclipse.core.AwsToolkitCore; +import com.amazonaws.eclipse.lambda.LambdaPlugin; + +public class SamLocalExecution implements ILaunchShortcut { + private static final String SAM_LOCAL_LAUNCH_CONFIGURATION_ID = "com.amazonaws.eclipse.lambda.launching.samLocal"; + + /** + * Launch SAM Local from project explorer + */ + @Override + public void launch(ISelection selection, String mode) { + if (selection instanceof IStructuredSelection) { + IStructuredSelection structuredSelection = (IStructuredSelection ) selection; + Object firstElement = structuredSelection.getFirstElement(); + if (firstElement instanceof IJavaElement) { + launch((IJavaElement)firstElement, LaunchMode.fromValue(mode)); + } + } + } + + /** + * Launch SAM Local from file editor + */ + @Override + public void launch(IEditorPart editor, String mode) { + IEditorInput editorInput = editor.getEditorInput(); + if (editorInput instanceof IFileEditorInput) { + IFile file = ((IFileEditorInput)editorInput).getFile(); + IJavaElement element = JavaCore.create(file); + if (element != null) { + launch(element, LaunchMode.fromValue(mode)); + } + } + } + + /** + * Central place for all entries of invoking SAM Local. + */ + public static void launch(IJavaElement javaElement, LaunchMode mode) { + try { + IProject project = javaElement.getJavaProject().getProject(); + ILaunchConfiguration configuration = getLaunchConfiguration(project, mode); + if (configuration == null) { + configuration = createLaunchConfiguration(project, mode); + ILaunchGroup group = DebugUITools.getLaunchGroup(configuration, mode.getMode()); + DebugUITools.openLaunchConfigurationDialog(Display.getCurrent().getActiveShell(), + configuration, group.getIdentifier(), null); + } else { + DebugUITools.launch(configuration, mode.getMode()); + } + } catch (Exception e) { + LambdaPlugin.getDefault().reportException("Failed to run SAM Locol", e); + } + } + + /** + * Create a new {@link ILaunchConfiguration} for the specified project. + */ + private static ILaunchConfiguration createLaunchConfiguration(IContainer basedir, LaunchMode mode) throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType launchConfigurationType = launchManager.getLaunchConfigurationType(SAM_LOCAL_LAUNCH_CONFIGURATION_ID); + + String configName = launchManager.generateLaunchConfigurationName("AWS SAM Local " + basedir.getName()); + ILaunchConfigurationWorkingCopy workingCopy = launchConfigurationType.newInstance(null, configName); + + workingCopy.setAttribute(A_PROJECT, basedir.getName()); + + return workingCopy.doSave(); + } + + /** + * Returns an existing {@link ILaunchConfiguration} based on the provided project. + * + * @return The only existing one or the chosen one {@link ILaunchConfiguration}, or null if no one exists. + * @throws CoreException + */ + private static ILaunchConfiguration getLaunchConfiguration(IContainer basedir, LaunchMode mode) throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType launchConfigurationType = launchManager + .getLaunchConfigurationType(SAM_LOCAL_LAUNCH_CONFIGURATION_ID); + + if (launchConfigurationType == null) { + return null; + } + + // Scan existing launch configurations + ILaunchConfiguration[] launchConfigurations = launchManager.getLaunchConfigurations(launchConfigurationType); + + List matchingConfigs = Arrays.stream(launchConfigurations).filter(config -> { + try { + String projectName = config.getAttribute(A_PROJECT, (String) null); + return projectName != null && projectName.equals(basedir.getName()); + } catch (CoreException e) { + return false; + } + }).collect(Collectors.toList()); + + if (matchingConfigs.size() == 1) { + return matchingConfigs.get(0); + } else if (matchingConfigs.size() > 1) { + IDebugModelPresentation labelProvider = DebugUITools.newDebugModelPresentation(); + ElementListSelectionDialog dialog = new ElementListSelectionDialog( + AwsToolkitCore.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell(), labelProvider); + dialog.setElements(matchingConfigs.toArray(new ILaunchConfiguration[matchingConfigs.size()])); + dialog.setTitle("Select SAM Local Configuration"); + dialog.setMessage("Choose which launch profile to use"); + dialog.setMultipleSelection(false); + int result = dialog.open(); + labelProvider.dispose(); + return result == Window.OK ? (ILaunchConfiguration) dialog.getFirstResult() : null; + } + return null; + } + + public static enum LaunchMode { + RUN("run"), + DEBUG("debug"), + ; + + private final String mode; + private LaunchMode(String mode) { + this.mode = mode; + } + + public static LaunchMode fromValue(String mode) { + for (LaunchMode launchMode : LaunchMode.values()) { + if (launchMode.getMode().equals(mode)) { + return launchMode; + } + } + throw new IllegalArgumentException(mode + " is not a valid mode for running AWS SAM Local."); + } + + public String getMode() { + return mode; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalPathFinder.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalPathFinder.java new file mode 100644 index 00000000..c99e91d8 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalPathFinder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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.launching; + +import java.io.File; +import java.util.Arrays; + +import org.apache.maven.model.Model; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.m2e.core.MavenPlugin; + +import com.amazonaws.eclipse.core.util.PluginUtils; + +public class SamLocalPathFinder { + private static final String[] TEMPLATE_FILES = { + "template.yaml", "template.yml", "serverless.template", "serverless.json", "template.json", "sam.template", "sam.json" + }; + private static final String[] ENV_VAR_FILES = {"envvars.json", "envvar.json", "env-vars.json"}; + private static final String[] EVENT_FILES = {"s3-event.json", "sns-event.json", "kinesis-event.json", "dynamodb-event.json", "api-event.json", "schedule-event.json"}; + + public static String findTemplateFile(String projectName) { + return find(projectName, TEMPLATE_FILES); + } + + public static String findEnvVarFile(String projectName) { + return find(projectName, ENV_VAR_FILES); + } + + public static String findEventFile(String projectName) { + return find(projectName, EVENT_FILES); + } + + /** + * Parse the project pom.xml file to generate the location for the build artifact. + */ + public static String findCodeUri(String projectName) { + if (projectName == null || projectName.isEmpty()) { + return ""; + } + + IFile pomFile = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).getFile("pom.xml"); + if (pomFile.exists()) { + try { + Model mavenModel = MavenPlugin.getMavenModelManager().readMavenModel(pomFile); + + String artifactId = mavenModel.getArtifactId(); + String version = mavenModel.getVersion(); + return String.format("./target/%s-%s.jar", artifactId, version); + } catch (CoreException e) { + return ""; + } + } + + return ""; + } + + private static String variablePluginReplace(String path) { + try { + return PluginUtils.variablePluginReplace(path); + } catch (CoreException e) { + return path; + } + } + + private static boolean fileExists(String path) { + File file = new File(path); + return file.exists() && file.isFile(); + } + + private static String find(String projectName, String[] candidatePaths) { + if (projectName == null || projectName.isEmpty()) { + return ""; + } + return Arrays.stream(candidatePaths) + .map(subpath -> PluginUtils.variablePluginGenerateWorkspacePath(projectName + "/" + subpath)) + .filter(path -> fileExists(variablePluginReplace(path))) + .findFirst().orElse(""); + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTab.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTab.java new file mode 100644 index 00000000..b4b752b3 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTab.java @@ -0,0 +1,554 @@ +/* + * Copyright 2017 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.launching; + +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_ACTION; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_CODE_URI; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_DEBUG_PORT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_ENV_VARS; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_EVENT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_HOST; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_LAMBDA_IDENTIFIER; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_MAVEN_GOALS; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PORT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PROFILE; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PROJECT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_REGION; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_SAM; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_TEMPLATE; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_TIME_OUT; + +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.core.databinding.AggregateValidationStatus; +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.databinding.beans.PojoProperties; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +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.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.PreferencesUtil; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; + +import com.amazonaws.eclipse.core.AwsToolkitCore; +import com.amazonaws.eclipse.core.regions.RegionUtils; +import com.amazonaws.eclipse.core.regions.ServiceAbbreviations; +import com.amazonaws.eclipse.core.ui.AccountSelectionComposite; +import com.amazonaws.eclipse.core.ui.ImportFileComposite; +import com.amazonaws.eclipse.core.ui.RegionComposite; +import com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory; +import com.amazonaws.eclipse.core.util.PluginUtils; +import com.amazonaws.eclipse.core.validator.FilePathValidator; +import com.amazonaws.eclipse.core.validator.IntegerRangeValidator; +import com.amazonaws.eclipse.core.validator.WorkspacePathValidator; +import com.amazonaws.eclipse.core.widget.ComboViewerComplex; +import com.amazonaws.eclipse.core.widget.TextComplex; +import com.amazonaws.eclipse.databinding.NotEmptyValidator; +import com.amazonaws.eclipse.lambda.LambdaConstants; +import com.amazonaws.eclipse.lambda.LambdaPlugin; +import com.amazonaws.eclipse.lambda.dialog.SamLocalGenerateEventDialog; +import com.amazonaws.eclipse.lambda.dialog.SamLocalGenerateEventDialog.SamLocalLambdaEventDataModel; +import com.amazonaws.eclipse.lambda.preferences.SamLocalPreferencePage; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel.SamAction; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel.SamLocalInvokeFunctionDataModel; +import com.amazonaws.eclipse.lambda.project.wizard.model.RunSamLocalDataModel.SamLocalStartApiDataModel; +import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessModel; +import com.amazonaws.eclipse.lambda.serverless.validator.ServerlessTemplateFilePathValidator; + +public class SamLocalTab extends AbstractLaunchConfigurationTab { + private final RunSamLocalDataModel dataModel = new RunSamLocalDataModel(); + + private final SamLocalInvokeFunctionDataModel invokeDataModel = new SamLocalInvokeFunctionDataModel(); + private final SamLocalStartApiDataModel startApiDataModel = new SamLocalStartApiDataModel(); + + private final DataBindingContext bindingContext; + private final AggregateValidationStatus aggregateValidationStatus; + + // UI + // AWS specific UI + private AccountSelectionComposite accountComposite; + private RegionComposite regionComposite; + + // SAM Local UI for common settings + private ImportFileComposite workspaceComposite; + private TextComplex mavenGoalsComplex; + private TextComplex samLocalExecutableComplex; + private ImportFileComposite templateFileComposite; + private ImportFileComposite envvarFileComposite; + private TextComplex debugPortComplex; + + // UI for Lambda function settings + private TextComplex codeUriComplex; + private TextComplex timeoutComplex; + + private ComboViewerComplex samCommandsComboViewer; + private Group samCommandGroup; + + // SAM Local invoke specific UI + private ComboViewerComplex lambdaPhysicalIdCombo; + private ImportFileComposite eventFileComposite; + + // SAM Local start-api specific UI + private TextComplex hostComplex; + private TextComplex portComplex; + + private ExpandableComposite advancedSettingsExpandable; + + public SamLocalTab() { + this.bindingContext = new DataBindingContext(); + this.aggregateValidationStatus = new AggregateValidationStatus( + bindingContext, AggregateValidationStatus.MAX_SEVERITY); + } + + @Override + public boolean isValid(ILaunchConfiguration launchConfig) { + setErrorMessage(null); + + IStatus status = getValidationStatus(); + if (status == null) { + return true; + } + + if (status.getSeverity() != IStatus.OK) { + setErrorMessage(status.getMessage()); + return false; + } + return true; + } + + private void entriesChanges() { + setDirty(true); + updateLaunchConfigurationDialog(); + } + + private IStatus getValidationStatus() { + if (aggregateValidationStatus == null) return null; + Object value = aggregateValidationStatus.getValue(); + if (!(value instanceof IStatus)) return null; + return (IStatus)value; + } + + private void createAwsScopeGroup(Composite composite) { + Group group = WizardWidgetFactory.newGroup(composite, "AWS Configuration"); + accountComposite = new AccountSelectionComposite(group, SWT.NONE); + accountComposite.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + dataModel.setAccount(AwsToolkitCore.getDefault().getAccountManager() + .getAccountInfo(accountComposite.getSelectedAccountId())); + entriesChanges(); + } + }); + regionComposite = RegionComposite.builder() + .parent(group) + .bindingContext(bindingContext) + .dataModel(dataModel.getRegionDataModel()) + .serviceName(ServiceAbbreviations.LAMBDA) + .addListener(e -> entriesChanges()) + .build(); + } + + private void createAdvancedSettingsGroup(Composite composite) { + Group group = WizardWidgetFactory.newGroup(composite, "SAM Local Configuration"); + + mavenGoalsComplex = TextComplex.builder(WizardWidgetFactory.newComposite(group, 1, 2, false), + bindingContext, PojoProperties.value(RunSamLocalDataModel.P_MAVEN_GOALS).observe(dataModel)) + .defaultValue(dataModel.getMavenGoals()) + .labelValue("Maven goals: ") + .addValidator(new NotEmptyValidator("Maven goals must not be empty!")) + .modifyListener(e -> entriesChanges()) + .textMessage("Maven goals for generating the uber jar of your Lambda project.") + .build(); + + Composite samLocalRuntimeComposite = WizardWidgetFactory.newComposite(group, 1, 3); + samLocalExecutableComplex = TextComplex.builder( + samLocalRuntimeComposite, bindingContext, PojoProperties.value( + RunSamLocalDataModel.P_SAM_RUNTIME).observe(dataModel)) + .createLabel(true) + .labelValue("SAM runtime: ") + .addValidator(new FilePathValidator("SAM runtime")) + .textMessage("AWS SAM Local executable path.") + .modifyListener(e -> entriesChanges()) + .build(); + + // Only disable this Text widget but not the validation. + samLocalExecutableComplex.getText().setEditable(false); + + Link link = new Link(samLocalRuntimeComposite, SWT.NONE); + link.setText("Configure AWS SAM Local..."); + link.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + PreferencesUtil.createPreferenceDialogOn(Display.getDefault().getActiveShell(), + SamLocalPreferencePage.ID, new String[] { SamLocalPreferencePage.ID }, null).open(); + samLocalExecutableComplex.setText(LambdaPlugin.getDefault().getPreferenceStore().getString( + SamLocalConstants.P_SAM_LOCAL_EXECUTABLE)); + } + }); + + debugPortComplex = TextComplex.builder(WizardWidgetFactory.newComposite(group, 1, 2, false), + bindingContext, PojoProperties.value(RunSamLocalDataModel.P_DEBUG_PORT).observe(dataModel)) + .defaultValue(String.valueOf(dataModel.getDebugPort())) + .labelValue("Debug port: ") + .addValidator(new IntegerRangeValidator("Debug port", 1024, 65535)) + .modifyListener(e -> entriesChanges()) + .textMessage("When specified, Lambda function container will start in debug mode and will expose this port on localhost.") + .build(); + + envvarFileComposite = ImportFileComposite.builder(group, bindingContext, dataModel.getEnvvarFileLocationDataModel()) + .textLabel("Env vars: ") + .filePathValidator(new WorkspacePathValidator("Env vars file", true)) + .modifyListener(e -> entriesChanges()) + .textMessage("JSON file containing values for Lambda function's environment variables.") + .buildWorkspaceFileBrowser(); + } + + private void createLambdaFunctionGroup(Composite composite) { + Group group = WizardWidgetFactory.newGroup(composite, "Lambda Function Configuration"); + + codeUriComplex = TextComplex.builder(WizardWidgetFactory.newComposite(group, 1, 2, false), + bindingContext, PojoProperties.value(RunSamLocalDataModel.P_CODE_URI).observe(dataModel)) + .labelValue("Code URI: ") + .modifyListener(e -> entriesChanges()) + .textMessage("Location to the function code as a Lambda deployment package.") + .build(); + + timeoutComplex = TextComplex.builder(WizardWidgetFactory.newComposite(group, 1, 2, false), + bindingContext, PojoProperties.value(RunSamLocalDataModel.P_TIME_OUT).observe(dataModel)) + .defaultValue(String.valueOf(RunSamLocalDataModel.DEFAULT_TIME_OUT)) + .addValidator(new IntegerRangeValidator("Lambda function timeout", 0, 300)) + .labelValue("Timeout (secs): ") + .modifyListener(e -> entriesChanges()) + .textMessage("Lambda function execution time (in seconds) after which Lambda terminates the function.") + .build(); + } + + private void createSamLocalConfigSection(Composite composite) { + Group group = WizardWidgetFactory.newGroup(composite, "SAM Local Configuration"); + + WizardWidgetFactory.newLink( + group, + LambdaConstants.webLinkListener, + "AWS Toolkit for Eclipse is using AWS SAM Local for locally debugging your Lambda function. You need to preinstall Docker and SAM for this feature.", 1, 100, 30); + + workspaceComposite = ImportFileComposite.builder(group, bindingContext, dataModel.getWorkspaceDataModel()) + .textLabel("Project:") + .filePathValidator(new NotEmptyValidator("Project must be specified!")) + .modifyListener(e -> { + entriesChanges(); + onProjectSelectChanged(); + }) + .textMessage("Target SAM project which must be a Maven project.") + .buildWorkspaceProjectBrowser(); + + templateFileComposite = ImportFileComposite.builder(group, bindingContext, dataModel.getTemplateFileLocationDataModel()) + .textLabel("Template:") + .filePathValidator(new ServerlessTemplateFilePathValidator()) + .modifyListener(e -> { + entriesChanges(); + onSamLocalTemplateFileChanged(); + }) + .textMessage("AWS SAM template file (default: \"serverless.[template|json], sam.[template|json]\")") + .buildWorkspaceFileBrowser(); + + samCommandsComboViewer = ComboViewerComplex.builder() + .composite(WizardWidgetFactory.newComposite(group, 1, 2, false)) + .bindingContext(bindingContext) + .pojoObservableValue(PojoProperties.value(RunSamLocalDataModel.P_SAM_ACTION).observe(dataModel)) + .addListeners((e) -> { + onSamCommandsComboViewerSelect(); + entriesChanges(); + }) + .items(SamAction.toList()) + .labelValue("Run as: ") + .labelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof SamAction) { + SamAction samAction = (SamAction) element; + return samAction.getDescription(); + } else { + return super.getText(element); + } + } + }) + .build(); + } + + private void onSamCommandsComboViewerSelect() { + switch (dataModel.getSamAction()) { + case INVOKE: + dataModel.setActionDataModel(invokeDataModel); + createInvokeSection(); + break; + case START_API: + dataModel.setActionDataModel(startApiDataModel); + createStartApiSection(); + break; + } + } + + private void onProjectSelectChanged() { + String projectName = dataModel.getWorkspaceDataModel().getFilePath(); + String codeUri = SamLocalPathFinder.findCodeUri(projectName); + codeUriComplex.setText(codeUri); + } + + private void onSamLocalTemplateFileChanged() { + if (lambdaPhysicalIdCombo == null) { + return; + } + + Collection data = getLambdaFunctionPhysicalIDs(); + lambdaPhysicalIdCombo.getComboViewer().setInput(data); + if (!data.isEmpty()) { + lambdaPhysicalIdCombo.selectItem(data.iterator().next()); + } + lambdaPhysicalIdCombo.getComboViewer().refresh(); + } + + private static final String UNAVAILABLE_PHYSICAL_ID = "Lambda function not found..."; + + private Collection getLambdaFunctionPhysicalIDs() { + ServerlessTemplateFilePathValidator validator = new ServerlessTemplateFilePathValidator(); + try { + ServerlessModel model = validator.validateFilePath(dataModel.getTemplateFileLocationDataModel().getFilePath()); + return model.getServerlessFunctions().keySet(); + } catch (Exception e) { + return Arrays.asList(UNAVAILABLE_PHYSICAL_ID); + } + } + + private void createSamLocalActionSection(Composite parent) { + samCommandGroup = WizardWidgetFactory.newGroup(parent, "SAM Local Command Configuration"); + onSamCommandsComboViewerSelect(); + } + + private void createInvokeSection() { + for (Control control : samCommandGroup.getChildren()) { + control.dispose(); + } + + hostComplex = null; + portComplex = null; + + lambdaPhysicalIdCombo = ComboViewerComplex.builder() + .bindingContext(bindingContext) + .composite(WizardWidgetFactory.newComposite(samCommandGroup, 1, 2, false)) + .labelValue("Function identifier: ") + .labelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof String) { + String text = (String) element; + return text; + } + return super.getText(element); + } + }) + .pojoObservableValue(PojoProperties.value(invokeDataModel.P_LAMBDA_IDENTIFIER).observe(invokeDataModel)) + .build(); + onSamLocalTemplateFileChanged(); + + Composite composite = WizardWidgetFactory.newComposite(samCommandGroup, 1, 2, false); + eventFileComposite = ImportFileComposite.builder(composite, bindingContext, invokeDataModel.getEventFileLocationDataModel()) + .textLabel("Event:") + .filePathValidator(new WorkspacePathValidator("Event", false)) + .modifyListener(e -> entriesChanges()) + .textMessage("JSON file containing event data passed to the Lambda function during invoke") + .buildWorkspaceFileBrowser(); + Button generateEventButton = WizardWidgetFactory.newPushButton(composite, "Generate", 1); + generateEventButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + SamLocalGenerateEventDialog generateEventDialog = new SamLocalGenerateEventDialog(getShell()); + int returnValue = generateEventDialog.open(); + if (returnValue == Window.OK) { + SamLocalLambdaEventDataModel dataModel = generateEventDialog.getDataModel(); + IPath resultPath = dataModel.getResultPath(); + eventFileComposite.setFilePath(PluginUtils.variablePluginGenerateWorkspacePath(resultPath)); + } + } + }); + samCommandGroup.layout(); + } + + private void createStartApiSection() { + for (Control control : samCommandGroup.getChildren()) { + control.dispose(); + } + + lambdaPhysicalIdCombo = null; + eventFileComposite = null; + + Composite composite = WizardWidgetFactory.newComposite(samCommandGroup, 1, 2); + hostComplex = TextComplex.builder(composite, bindingContext, PojoProperties.value(SamLocalStartApiDataModel.P_HOST).observe(startApiDataModel)) + .defaultValue(startApiDataModel.getHost()) + .labelValue("Host:") + .textMessage("Local hostname or IP address to bind to (default: \"127.0.0.1\")") + .modifyListener(e -> entriesChanges()) + .build(); + + portComplex = TextComplex.builder(composite, bindingContext, PojoProperties.value(SamLocalStartApiDataModel.P_PORT).observe(startApiDataModel)) + .defaultValue(String.valueOf(startApiDataModel.getPort())) + .labelValue("Port:") + .textMessage("Local port number to listen on (default: \"3000\")") + .modifyListener(e -> entriesChanges()) + .build(); + + samCommandGroup.layout(); + } + + @Override + public void createControl(Composite parent) { + Composite rootComposite = WizardWidgetFactory.newComposite(parent, 1, 1); + setControl(rootComposite); + + rootComposite.setLayout(new GridLayout(1, false)); + + createSamLocalConfigSection(rootComposite); + createSamLocalActionSection(rootComposite); + + createAdvancedSettingSection(rootComposite); + dataModel.setAccount(AwsToolkitCore.getDefault().getAccountManager() + .getAccountInfo(accountComposite.getSelectedAccountId())); + } + + private void createAdvancedSettingSection(final Composite parent) { + advancedSettingsExpandable = new ExpandableComposite(parent, SWT.NONE, + ExpandableComposite.TWISTIE | ExpandableComposite.COMPACT | ExpandableComposite.EXPANDED); + advancedSettingsExpandable.setText("Advanced Settings"); + advancedSettingsExpandable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite expandableComposite = WizardWidgetFactory.newComposite(advancedSettingsExpandable, 1, 1); + advancedSettingsExpandable.setClient(expandableComposite); + advancedSettingsExpandable.addExpansionListener(new ExpansionAdapter() { + @Override + public void expansionStateChanged(ExpansionEvent e) { + Shell shell = parent.getShell(); + shell.layout(true, true); + } + }); + + createAwsScopeGroup(expandableComposite); + createAdvancedSettingsGroup(expandableComposite); + createLambdaFunctionGroup(expandableComposite); + } + + @Override + public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { + } + + @Override + public void initializeFrom(ILaunchConfiguration configuration) { + accountComposite.selectAccountName(getAttribute(configuration, A_PROFILE, RunSamLocalDataModel.DEFAULT_PROFILE)); + dataModel.setAccount(AwsToolkitCore.getDefault().getAccountManager() + .getAccountInfo(accountComposite.getSelectedAccountId())); + + regionComposite.selectAwsRegion(RegionUtils.getRegion(getAttribute(configuration, A_REGION, RunSamLocalDataModel.DEFAULT_REGION))); + String projectName = getAttribute(configuration, A_PROJECT, ""); + workspaceComposite.setFilePath(projectName); + + mavenGoalsComplex.setText(getAttribute(configuration, A_MAVEN_GOALS, RunSamLocalDataModel.DEFAULT_MAVEN_GOALS)); + + samLocalExecutableComplex.setText(getAttribute(configuration, A_SAM, + LambdaPlugin.getDefault().getPreferenceStore().getString(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE))); + + debugPortComplex.setText(getAttribute(configuration, A_DEBUG_PORT, String.valueOf(RunSamLocalDataModel.DEFAULT_DEBUG_PORT))); + templateFileComposite.setFilePath(getAttribute(configuration, A_TEMPLATE, SamLocalPathFinder.findTemplateFile(projectName))); + envvarFileComposite.setFilePath(getAttribute(configuration, A_ENV_VARS, "")); + + codeUriComplex.setText(getAttribute(configuration, A_CODE_URI, SamLocalPathFinder.findCodeUri(projectName))); + timeoutComplex.setText(getAttribute(configuration, A_TIME_OUT, String.valueOf(RunSamLocalDataModel.DEFAULT_TIME_OUT))); + + SamAction samCommand = SamAction.fromValue(getAttribute(configuration, A_ACTION, SamAction.INVOKE.getName())); + samCommandsComboViewer.selectItem(samCommand); + + switch (samCommand) { + case INVOKE: + lambdaPhysicalIdCombo.selectItem(getAttribute(configuration, A_LAMBDA_IDENTIFIER, (String) null)); + eventFileComposite.setFilePath(getAttribute(configuration, A_EVENT, SamLocalPathFinder.findEventFile(projectName))); + break; + case START_API: + hostComplex.setText(getAttribute(configuration, A_HOST, SamLocalStartApiDataModel.DEFAULT_HOST)); + portComplex.setText(getAttribute(configuration, A_PORT, String.valueOf(SamLocalStartApiDataModel.DEFAULT_PORT))); + break; + } + + advancedSettingsExpandable.setExpanded(false); + setDirty(false); + } + + @Override + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute(A_PROFILE, dataModel.getAccount().getAccountName()); + configuration.setAttribute(A_REGION, dataModel.getRegionDataModel().getRegionId()); + configuration.setAttribute(A_PROJECT, dataModel.getWorkspaceDataModel().getFilePath()); + configuration.setAttribute(A_MAVEN_GOALS, dataModel.getMavenGoals()); + configuration.setAttribute(A_SAM, dataModel.getSamRuntime()); + configuration.setAttribute(A_DEBUG_PORT, String.valueOf(dataModel.getDebugPort())); + configuration.setAttribute(A_TEMPLATE, dataModel.getTemplateFileLocationDataModel().getFilePath()); + configuration.setAttribute(A_ENV_VARS, dataModel.getEnvvarFileLocationDataModel().getFilePath()); + + configuration.setAttribute(A_CODE_URI, dataModel.getCodeUri()); + configuration.setAttribute(A_TIME_OUT, String.valueOf(dataModel.getTimeOut())); + + SamAction samAction = dataModel.getSamAction(); + configuration.setAttribute(A_ACTION, samAction.getName()); + dataModel.getActionDataModel().toAttributeMap().forEach(configuration::setAttribute); + } + + @Override + public String getName() { + return "Main"; + } + + @Override + public Image getImage() { + return LambdaPlugin.getDefault().getImageRegistry().getDescriptor(LambdaPlugin.IMAGE_SAM_LOCAL).createImage(); + } + + private String getAttribute(ILaunchConfiguration configuration, String name, String defaultValue) { + try { + return configuration.getAttribute(name, defaultValue); + } catch (CoreException ex) { + return defaultValue; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTabGroup.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTabGroup.java new file mode 100644 index 00000000..b71d186d --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/launching/SamLocalTabGroup.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 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.launching; + +import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; +import org.eclipse.debug.ui.CommonTab; +import org.eclipse.debug.ui.ILaunchConfigurationDialog; +import org.eclipse.debug.ui.ILaunchConfigurationTab; + +public class SamLocalTabGroup extends AbstractLaunchConfigurationTabGroup { + + @Override + public void createTabs(ILaunchConfigurationDialog dialog, String mode) { + setTabs(new ILaunchConfigurationTab[] { + new SamLocalTab(), + new CommonTab() + }); + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/PreferenceInitializer.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/PreferenceInitializer.java new file mode 100644 index 00000000..e1c92d7f --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/PreferenceInitializer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 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.preferences; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; + +import com.amazonaws.eclipse.core.util.OsPlatformUtils; +import com.amazonaws.eclipse.lambda.LambdaPlugin; +import com.amazonaws.eclipse.lambda.launching.SamLocalConstants; + +public class PreferenceInitializer extends AbstractPreferenceInitializer { + private static final String SAM_LOCAL_WINDOWS_DEFAULT_LOCATION = + String.format("C:\\Users\\%s\\AppData\\Roaming\\npm\\sam.exe", OsPlatformUtils.currentUser()); + private static final String SAM_LOCAL_LINUX_DEFAULT_LOCATION = "/usr/local/bin/sam"; + + @Override + public void initializeDefaultPreferences() { + IPreferenceStore store = LambdaPlugin.getDefault().getPreferenceStore(); + store.setDefault(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE, getDefaultSamLocalLocation()); + } + + private String getDefaultSamLocalLocation() { + if (OsPlatformUtils.isWindows()) { + return SAM_LOCAL_WINDOWS_DEFAULT_LOCATION; + } else { + return SAM_LOCAL_LINUX_DEFAULT_LOCATION; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/SamLocalPreferencePage.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/SamLocalPreferencePage.java new file mode 100644 index 00000000..a9f44d07 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/preferences/SamLocalPreferencePage.java @@ -0,0 +1,132 @@ +/* + * Copyright 2017 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.preferences; + +import org.eclipse.core.databinding.AggregateValidationStatus; +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +import com.amazonaws.eclipse.core.model.ImportFileDataModel; +import com.amazonaws.eclipse.core.ui.ImportFileComposite; +import com.amazonaws.eclipse.core.ui.preferences.AwsToolkitPreferencePage; +import com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory; +import com.amazonaws.eclipse.core.validator.FilePathValidator; +import com.amazonaws.eclipse.lambda.LambdaConstants; +import com.amazonaws.eclipse.lambda.LambdaPlugin; +import com.amazonaws.eclipse.lambda.launching.SamLocalConstants; + +public class SamLocalPreferencePage extends AwsToolkitPreferencePage implements IWorkbenchPreferencePage { + private static final String PAGE_NAME = SamLocalPreferencePage.class.getName(); + public static final String ID = "com.amazonaws.eclipse.lambda.preferences.SamLocalPreferencePage"; + + private SamLocalPreferencesDataModel dataModel = new SamLocalPreferencesDataModel(); + private DataBindingContext dataBindingContext = new DataBindingContext(); + private AggregateValidationStatus aggregateValidationStatus = + new AggregateValidationStatus(dataBindingContext, AggregateValidationStatus.MAX_SEVERITY); + + private ImportFileComposite samLocalExecutableComposite; + + public SamLocalPreferencePage() { + super(PAGE_NAME); + setDescription("AWS SAM Local Configuration"); + this.dispose(); + } + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(LambdaPlugin.getDefault().getPreferenceStore()); + aggregateValidationStatus.addChangeListener(e -> { + Object value = aggregateValidationStatus.getValue(); + if (value instanceof IStatus == false) return; + + IStatus status = (IStatus)value; + boolean success = (status.getSeverity() == IStatus.OK); + if (success) { + setErrorMessage(null); + } else { + setErrorMessage(status.getMessage()); + } + setValid(success); + }); + initDataModel(); + } + + private void initDataModel() { + dataModel.getSamLocalExecutable().setFilePath( + getPreferenceStore().getString(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE)); + } + + @Override + protected Control createContents(Composite parent) { + Composite composite = WizardWidgetFactory.newComposite(parent, 1, 1); + + WizardWidgetFactory.newLink( + composite, + LambdaConstants.webLinkListener, + "To install AWS SAM Local and the required dependencies, see Installation.", 1, 100, 30); + + samLocalExecutableComposite = ImportFileComposite.builder( + composite, dataBindingContext, dataModel.getSamLocalExecutable()) + .buttonLabel("Browse...") + .filePathValidator(new FilePathValidator("SAM Local Executbale")) + .textLabel("SAM Local Executable:") + .textMessage("Absolute path for sam command...") + .build(); + + return composite; + } + + @Override + protected void performDefaults() { + if (samLocalExecutableComposite != null) { + samLocalExecutableComposite.setFilePath( + getPreferenceStore().getDefaultString(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE)); + } + super.performDefaults(); + } + + @Override + public boolean performOk() { + aggregateValidationStatus.dispose(); + dataBindingContext.dispose(); + onApplyButton(); + return super.performOk(); + } + + @Override + protected void performApply() { + onApplyButton(); + super.performApply(); + } + + private void onApplyButton() { + String filePath = dataModel.getSamLocalExecutable().getFilePath(); + getPreferenceStore().setValue(SamLocalConstants.P_SAM_LOCAL_EXECUTABLE, filePath); + } + + private static class SamLocalPreferencesDataModel { + private final ImportFileDataModel samLocalExecutable = new ImportFileDataModel(); + + public ImportFileDataModel getSamLocalExecutable() { + return samLocalExecutable; + } + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/NewServerlessProjectDataModel.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/NewServerlessProjectDataModel.java index e56b74ef..421be8e2 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/NewServerlessProjectDataModel.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/NewServerlessProjectDataModel.java @@ -155,7 +155,7 @@ private void buildServerlessModel() throws JsonParseException, JsonMappingExcept ServerlessBlueprint blueprint = getSelectedBlueprint(); String content = CodeTemplateManager.processTemplateWithData( CodeTemplateManager.getInstance().getServerlessSamTemplate(blueprint), getServerlessSamTemplateData()); - serverlessModel = Serverless.load(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + serverlessModel = Serverless.loadFromContent(content); } else { serverlessModel = Serverless.load(importFileDataModel.getFilePath()); } diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/RunSamLocalDataModel.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/RunSamLocalDataModel.java new file mode 100644 index 00000000..fae0b952 --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/model/RunSamLocalDataModel.java @@ -0,0 +1,242 @@ +/* + * Copyright 2017 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.model; + +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_EVENT; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_HOST; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_LAMBDA_IDENTIFIER; +import static com.amazonaws.eclipse.lambda.launching.SamLocalConstants.A_PORT; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.amazonaws.eclipse.core.AccountInfo; +import com.amazonaws.eclipse.core.model.ImportFileDataModel; +import com.amazonaws.eclipse.core.model.RegionDataModel; + +/** + * Data model for debugging Sam function using SAM Local. + */ +public class RunSamLocalDataModel { + public static final int DEFAULT_DEBUG_PORT = 5858; + public static final String DEFAULT_MAVEN_GOALS = "clean package"; + public static final String DEFAULT_PROFILE = "default"; + public static final String DEFAULT_REGION = "us-east-1"; + public static final int DEFAULT_TIME_OUT = 300; + + public static final String P_DEBUG_PORT = "debugPort"; + public static final String P_SAM_ACTION = "samAction"; + public static final String P_SAM_RUNTIME = "samRuntime"; + public static final String P_MAVEN_GOALS = "mavenGoals"; + public static final String P_CODE_URI = "codeUri"; + public static final String P_TIME_OUT = "timeOut"; + + private final RegionDataModel regionDataModel = new RegionDataModel(); + private final ImportFileDataModel workspaceDataModel = new ImportFileDataModel(); + private final ImportFileDataModel templateFileLocationDataModel = new ImportFileDataModel(); + private final ImportFileDataModel envvarFileLocationDataModel = new ImportFileDataModel(); + + private AccountInfo account; + private String mavenGoals = DEFAULT_MAVEN_GOALS; + private String samRuntime; + private int debugPort = DEFAULT_DEBUG_PORT; + private SamAction samAction = SamAction.INVOKE; + private AttributeMapConvertible actionDataModel; + private String codeUri; + private int timeOut = DEFAULT_TIME_OUT; + + public String getMavenGoals() { + return mavenGoals; + } + + public void setMavenGoals(String mavenGoals) { + this.mavenGoals = mavenGoals; + } + + public String getSamRuntime() { + return samRuntime; + } + + public void setSamRuntime(String samRuntime) { + this.samRuntime = samRuntime; + } + + public String getCodeUri() { + return codeUri; + } + + public void setCodeUri(String codeUri) { + this.codeUri = codeUri; + } + + public int getTimeOut() { + return timeOut; + } + + public void setTimeOut(int timeOut) { + this.timeOut = timeOut; + } + + public enum SamAction { + INVOKE("invoke", "Lambda Function"), + START_API("start-api", "API Gateway"), + ; + + // Action name must be a valid command line action `sam local ${action}` + private final String name; + private final String description; + + private SamAction(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public static List toList() { + return Arrays.asList(SamAction.values()); + } + + public static SamAction fromValue(String value) { + for (SamAction samAction : SamAction.values()) { + if (samAction.getName().equals(value)) { + return samAction; + } + } + return null; + } + } + + public AccountInfo getAccount() { + return account; + } + + public void setAccount(AccountInfo account) { + this.account = account; + } + + public RegionDataModel getRegionDataModel() { + return regionDataModel; + } + + public ImportFileDataModel getWorkspaceDataModel() { + return workspaceDataModel; + } + + public ImportFileDataModel getTemplateFileLocationDataModel() { + return templateFileLocationDataModel; + } + + public ImportFileDataModel getEnvvarFileLocationDataModel() { + return envvarFileLocationDataModel; + } + + public int getDebugPort() { + return debugPort; + } + + public void setDebugPort(int debugPort) { + this.debugPort = debugPort; + } + + public AttributeMapConvertible getActionDataModel() { + return actionDataModel; + } + + public void setActionDataModel(AttributeMapConvertible actionDataModel) { + this.actionDataModel = actionDataModel; + } + + public SamAction getSamAction() { + return samAction; + } + + public void setSamAction(SamAction samAction) { + this.samAction = samAction; + } + + public static class SamLocalInvokeFunctionDataModel implements AttributeMapConvertible { + public static final String P_LAMBDA_IDENTIFIER = "lambdaIdentifier"; + + private String lambdaIdentifier; + private final ImportFileDataModel eventFileLocationDataModel = new ImportFileDataModel(); + + public String getLambdaIdentifier() { + return lambdaIdentifier; + } + + public void setLambdaIdentifier(String lambdaIdentifier) { + this.lambdaIdentifier = lambdaIdentifier; + } + + public ImportFileDataModel getEventFileLocationDataModel() { + return eventFileLocationDataModel; + } + + @Override + public Map toAttributeMap() { + Map attributes = new HashMap<>(); + + attributes.put(A_EVENT, eventFileLocationDataModel.getFilePath()); + attributes.put(A_LAMBDA_IDENTIFIER, lambdaIdentifier); + + return attributes; + } + } + + public static class SamLocalStartApiDataModel implements AttributeMapConvertible { + public static final int DEFAULT_PORT = 3000; + public static final String DEFAULT_HOST = "127.0.0.1"; + + public static final String P_PORT = "port"; + public static final String P_HOST = "host"; + + private String host = DEFAULT_HOST; + private int port = DEFAULT_PORT; + + public String getHost() { + return host; + } + public void setHost(String host) { + this.host = host; + } + public int getPort() { + return port; + } + public void setPort(int port) { + this.port = port; + } + + @Override + public Map toAttributeMap() { + Map attributes = new HashMap<>(); + attributes.put(A_HOST, host); + attributes.put(A_PORT, String.valueOf(port)); + return attributes; + } + } + + public static interface AttributeMapConvertible { + Map toAttributeMap(); + } +} diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/page/NewServerlessProjectWizardPageOne.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/page/NewServerlessProjectWizardPageOne.java index 094af36c..3fa379ee 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/page/NewServerlessProjectWizardPageOne.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/page/NewServerlessProjectWizardPageOne.java @@ -203,8 +203,9 @@ private void onBlueprintSelectionViewerSelectionChange() { } private void createServerlessTemplateImportSection(Composite parent) { - importFileComposite = new ImportFileComposite( - parent, bindingContext, dataModel.getImportFileDataModel(), new ServerlessTemplateFilePathValidator()); + importFileComposite = ImportFileComposite.builder(parent, bindingContext, dataModel.getImportFileDataModel()) + .filePathValidator(new ServerlessTemplateFilePathValidator()) + .build(); } private void setServerlessTemplateImportSectionEnabled(boolean enabled) { diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/FunctionProjectUtil.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/FunctionProjectUtil.java index ef5f716e..1f85f8e6 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/FunctionProjectUtil.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/FunctionProjectUtil.java @@ -331,8 +331,7 @@ private static File addFileToProject(IPath targetPath, String fileName, String f * Return the absolute path of the specified location relative to the project. */ public static IPath getProjectDirectory(IProject project, String path) { - IPath workspaceRoot = project.getWorkspace().getRoot().getRawLocation(); - IPath projectRoot = workspaceRoot.append(project.getFullPath()); + IPath projectRoot = project.getLocation(); if (path == null) { return projectRoot; diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/LambdaFunctionComposite.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/LambdaFunctionComposite.java index 95dbf4a3..7b0dbf40 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/LambdaFunctionComposite.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/project/wizard/util/LambdaFunctionComposite.java @@ -114,10 +114,8 @@ public void createControl() { } public void createClassNameControl() { - this.classNameComplex = TextComplex.builder() - .composite(inputComposite) - .dataBindingContext(dataBindingContext) - .pojoObservableValue(PojoProperties.value(LambdaFunctionDataModel.class, P_CLASS_NAME).observe(dataModel)) + this.classNameComplex = TextComplex.builder(inputComposite, dataBindingContext, + PojoProperties.value(P_CLASS_NAME).observe(dataModel)) .addValidator(new NotEmptyValidator("Please provide a valid class name for the handler")) .labelValue("Class Name:") .defaultValue(dataModel.getClassName()) diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/Serverless.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/Serverless.java index f93a4a3d..b3be669b 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/Serverless.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/Serverless.java @@ -21,20 +21,24 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Stream; import com.amazonaws.eclipse.lambda.serverless.model.ServerlessTemplate; import com.amazonaws.eclipse.lambda.serverless.model.TypeProperties; import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessFunction; import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessModel; +import com.amazonaws.util.IOUtils; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; /** * A class for processing serverless template file, including loading Serverless template file into a model, @@ -42,34 +46,61 @@ */ public class Serverless { - private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ObjectMapper JSON_MAPPER = initMapper(new ObjectMapper()); + private static final ObjectMapper YAML_MAPPER = initMapper(new ObjectMapper(new YAMLFactory())); - static { - MAPPER.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); - MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); - MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + private static ObjectMapper initMapper(ObjectMapper mapper) { + mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); + mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return mapper; } public static ServerlessModel load(String templatePath) throws JsonParseException, JsonMappingException, IOException { return load(new File(templatePath)); } + // Load Serverless model from the given template file, and close the input stream safely. public static ServerlessModel load(File templateFile) throws JsonParseException, JsonMappingException, IOException { - return load(new FileInputStream(templateFile)); + try (InputStream inputStream = new FileInputStream(templateFile)) { + return load(inputStream); + } } public static ServerlessModel load(InputStream templateInput) throws JsonParseException, JsonMappingException, IOException { - ServerlessTemplate serverlessTemplate = MAPPER.readValue(templateInput, ServerlessTemplate.class); + String content = IOUtils.toString(templateInput); + return loadFromContent(content); + } + + public static ServerlessModel loadFromContent(String content) throws JsonParseException, JsonMappingException, IOException { + ObjectMapper mapper = isContentInJsonFormat(content) ? JSON_MAPPER : YAML_MAPPER; + ServerlessTemplate serverlessTemplate = mapper.readValue(content.getBytes(StandardCharsets.UTF_8), ServerlessTemplate.class); return convert(serverlessTemplate); } public static File write(ServerlessModel model, String path) throws JsonGenerationException, JsonMappingException, IOException { File file = new File(path); ServerlessTemplate template = convert(model); - MAPPER.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(file), template); + JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(file), template); return file; } + /** + * Return whether the underlying String content is in Json format based on the first non-whitespace character. + */ + private static boolean isContentInJsonFormat(String content) { + if (content == null || content.isEmpty()) { + return false; + } + for (int index = 0; index < content.length(); ++index) { + if (Character.isWhitespace(content.charAt(index))) { + continue; + } + return '{' == content.charAt(index); + } + return false; + } + /** * Make this raw Serverless model to one that could be recognized by CloudFormation. * @@ -148,7 +179,7 @@ private static void convertResource(ServerlessModel model, String resourceLogica private static ServerlessFunction convertServerlessFunction(TypeProperties tp) { Map resource = tp.getProperties(); - ServerlessFunction function = MAPPER.convertValue(resource, ServerlessFunction.class); + ServerlessFunction function = JSON_MAPPER.convertValue(resource, ServerlessFunction.class); for (Entry entry : tp.getAdditionalProperties().entrySet()) { function.addAdditionalTopLevelProperty(entry.getKey(), entry.getValue()); } @@ -158,7 +189,7 @@ private static ServerlessFunction convertServerlessFunction(TypeProperties tp) { private static Map convert(Map map) { Map typeProperties = new HashMap<>(); for (Entry entry : map.entrySet()) { - typeProperties.put(entry.getKey(), MAPPER.convertValue(entry.getValue(), TypeProperties.class)); + typeProperties.put(entry.getKey(), JSON_MAPPER.convertValue(entry.getValue(), TypeProperties.class)); } return typeProperties; } diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/handler/SamLocalHandler.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/handler/SamLocalHandler.java new file mode 100644 index 00000000..fbd3b85b --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/handler/SamLocalHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017 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.serverless.handler; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jdt.core.IJavaElement; + +import com.amazonaws.eclipse.lambda.launching.SamLocalExecution; +import com.amazonaws.eclipse.lambda.launching.SamLocalExecution.LaunchMode; +import com.amazonaws.eclipse.lambda.ui.LambdaJavaProjectUtil; + +/** + * Action of running/debugging SAM application when right clicking project explorer. + */ +public class SamLocalHandler { + + public static class RunSamLocalHandler extends AbstractHandler { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IJavaElement selectedJavaElement = LambdaJavaProjectUtil.getSelectedJavaElementFromCommandEvent(event); + SamLocalExecution.launch(selectedJavaElement, LaunchMode.RUN); + return null; + } + } + + public static class DebugSamLocalHandler extends AbstractHandler { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IJavaElement selectedJavaElement = LambdaJavaProjectUtil.getSelectedJavaElementFromCommandEvent(event); + SamLocalExecution.launch(selectedJavaElement, LaunchMode.DEBUG); + return null; + } + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/ServerlessTemplate.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/ServerlessTemplate.java index b8ac2902..b54b29ac 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/ServerlessTemplate.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/ServerlessTemplate.java @@ -15,6 +15,7 @@ package com.amazonaws.eclipse.lambda.serverless.model; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; @@ -24,7 +25,7 @@ public class ServerlessTemplate extends AdditionalProperties { private String AWSTemplateFormatVersion; @JsonProperty("Transform") - private String transform; + private List transform; @JsonProperty("Description") private String description; @@ -42,11 +43,11 @@ public void setAWSTemplateFormatVersion(String aWSTemplateFormatVersion) { AWSTemplateFormatVersion = aWSTemplateFormatVersion; } - public String getTransform() { + public List getTransform() { return transform; } - public void setTransform(String transform) { + public void setTransform(List transform) { this.transform = transform; } diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessFunction.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessFunction.java index 5c999bdb..bac6a243 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessFunction.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessFunction.java @@ -25,6 +25,7 @@ import com.amazonaws.eclipse.lambda.serverless.model.TypeProperties; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; public class ServerlessFunction extends Resource { @@ -52,7 +53,7 @@ public class ServerlessFunction extends Resource { private Integer timeout; @JsonProperty("Role") - private String role; + private JsonNode role; @JsonProperty("Policies") private final List policies = new ArrayList<>(); @@ -109,11 +110,11 @@ public void setTimeout(Integer timeout) { this.timeout = timeout; } - public String getRole() { + public JsonNode getRole() { return role; } - public void setRole(String role) { + public void setRole(JsonNode role) { this.role = role; } diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessModel.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessModel.java index 9a0a7622..f7fd9801 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessModel.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/model/transform/ServerlessModel.java @@ -14,7 +14,9 @@ */ package com.amazonaws.eclipse.lambda.serverless.model.transform; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.amazonaws.eclipse.lambda.serverless.model.AdditionalProperties; @@ -23,14 +25,15 @@ public class ServerlessModel extends AdditionalProperties { private static final String DEFAULT_AWS_TEMPLATE_FORMAT_VERSION = "2010-09-09"; - private static final String DEFAULT_TRANSFORM = "AWS::Serverless-2016-10-31"; + private static final List DEFAULT_TRANSFORM = Arrays.asList("AWS::Serverless-2016-10-31"); @JsonProperty("AWSTemplateFormatVersion") private String awsTemplateFormatVersion; - private String transform; + private List transform; private String description; private final Map serverlessFunctions = new HashMap<>(); + // A map of Lambda handler name to Lambda function physical ID private final Map serverlessFunctionPhysicalIds = new HashMap<>(); // Unrecognized resources private final Map additionalResources = new HashMap<>(); @@ -59,14 +62,14 @@ public void setAWSTemplateFormatVersion(String awsTemplateFormatVersion) { this.awsTemplateFormatVersion = awsTemplateFormatVersion; } - public String getTransform() { + public List getTransform() { if (transform == null) { transform = DEFAULT_TRANSFORM; } return transform; } - public void setTransform(String transform) { + public void setTransform(List transform) { this.transform = transform; } diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/validator/ServerlessTemplateFilePathValidator.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/validator/ServerlessTemplateFilePathValidator.java index 599c3584..68afc9b8 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/validator/ServerlessTemplateFilePathValidator.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/validator/ServerlessTemplateFilePathValidator.java @@ -14,27 +14,44 @@ */ package com.amazonaws.eclipse.lambda.serverless.validator; +import java.io.IOException; + import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; +import com.amazonaws.eclipse.core.util.PluginUtils; import com.amazonaws.eclipse.lambda.serverless.Serverless; +import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessModel; import com.amazonaws.util.StringUtils; public class ServerlessTemplateFilePathValidator implements IValidator { - @Override public IStatus validate(Object value) { - String s = (String)value; - if (StringUtils.isNullOrEmpty(s)) { - return ValidationStatus.error("Serverless template file path must be provided!"); - } + String filePath = (String) value; + try { - Serverless.load(s); + validateFilePath(filePath); } catch (Exception e) { return ValidationStatus.error(e.getMessage()); } return ValidationStatus.ok(); } -} + public ServerlessModel validateFilePath(String filePath) { + if (StringUtils.isNullOrEmpty(filePath)) { + throw new IllegalArgumentException("Serverless template file path must be provided!"); + } + try { + filePath = PluginUtils.variablePluginReplace(filePath); + ServerlessModel model = Serverless.load(filePath); + if (model.getAdditionalResources().isEmpty() && model.getServerlessFunctions().isEmpty()) { + throw new IllegalArgumentException("Serverless template file is not valid, you need to define at least one AWS Resource."); + } + return model; + } catch (IOException | CoreException e) { + throw new IllegalArgumentException(e); + } + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/wizard/editoraction/SamLocalAction.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/wizard/editoraction/SamLocalAction.java new file mode 100644 index 00000000..0bfef97d --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/serverless/wizard/editoraction/SamLocalAction.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 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.serverless.wizard.editoraction; + +import org.eclipse.jface.action.IAction; + +import com.amazonaws.eclipse.lambda.launching.SamLocalExecution; +import com.amazonaws.eclipse.lambda.launching.SamLocalExecution.LaunchMode; +import com.amazonaws.eclipse.lambda.upload.wizard.editoraction.AbstractLambdaEditorAction; + +/** + * Action of local debugging SAM application triggered by right clicking Lambda function editor + */ +public class SamLocalAction { + + public static class RunSamLocalAction extends AbstractLambdaEditorAction { + @Override + public void run(IAction action) { + SamLocalExecution.launch(selectedJavaElement, LaunchMode.RUN); + } + } + + public static class DebugSamLocalAction extends AbstractLambdaEditorAction { + @Override + public void run(IAction action) { + SamLocalExecution.launch(selectedJavaElement, LaunchMode.DEBUG); + } + } +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/ui/LambdaPluginColors.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/ui/LambdaPluginColors.java new file mode 100644 index 00000000..4917a4aa --- /dev/null +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/ui/LambdaPluginColors.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017 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.ui; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.widgets.Display; + +/** + * Predefined colors to be used by Lambda plugin. + */ +public class LambdaPluginColors { + public static final Color BLACK = new Color(Display.getCurrent(), 0, 0, 0); + public static final Color GREY = new Color(Display.getCurrent(), 214, 214, 214); +} \ No newline at end of file diff --git a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/upload/wizard/page/FunctionConfigurationPage.java b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/upload/wizard/page/FunctionConfigurationPage.java index 0ce1761a..29a04f30 100644 --- a/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/upload/wizard/page/FunctionConfigurationPage.java +++ b/bundles/com.amazonaws.eclipse.lambda/src/com/amazonaws/eclipse/lambda/upload/wizard/page/FunctionConfigurationPage.java @@ -29,7 +29,6 @@ import org.eclipse.core.databinding.AggregateValidationStatus; import org.eclipse.core.databinding.DataBindingContext; -import org.eclipse.core.databinding.beans.PojoObservables; import org.eclipse.core.databinding.beans.PojoProperties; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; @@ -198,15 +197,12 @@ private void createBasicSettingSection(Composite parent) { newLabel(group, "Name:"); functionNameLabel = newFillingLabel(group, "", 2); - descriptionTextComplex = TextComplex.builder() - .composite(group) + descriptionTextComplex = TextComplex.builder(group, + bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_DESCRIPTION).observe(dataModel.getFunctionConfigPageDataModel())) .createLabel(true) .labelColSpan(1) - .dataBindingContext(bindingContext) .labelValue("Description:") .textColSpan(2) - .pojoObservableValue(PojoObservables.observeValue( - dataModel.getFunctionConfigPageDataModel(), FunctionConfigPageDataModel.P_DESCRIPTION)) .defaultValue(dataModel.getFunctionConfigPageDataModel().getDescription()) .textMessage("The description for the function (optional)") .build(); @@ -239,7 +235,7 @@ private void createFunctionVersioningAndAliasSettingSection(Composite parent) { .dataBindingContext(bindingContext) .defaultValue(false) .labelValue("Publish new version") - .pojoObservableValue(PojoObservables.observeValue(dataModel.getFunctionConfigPageDataModel(), P_PUBLISH_NEW_VERSION)) + .pojoObservableValue(PojoProperties.value(P_PUBLISH_NEW_VERSION).observe(dataModel.getFunctionConfigPageDataModel())) .selectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { @@ -253,7 +249,7 @@ public void widgetSelected(SelectionEvent e) { .dataBindingContext(bindingContext) .defaultValue(false) .labelValue("Provide an alias to this new version") - .pojoObservableValue(PojoObservables.observeValue(dataModel.getFunctionConfigPageDataModel(), P_CREATE_NEW_VERSION_ALIAS)) + .pojoObservableValue(PojoProperties.value(P_CREATE_NEW_VERSION_ALIAS).observe(dataModel.getFunctionConfigPageDataModel())) .selectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { @@ -344,31 +340,25 @@ private void createAdvancedSettingSection(Composite parent) { "Please enter a timeout within the range of [%d, %d]", MIN_TIMEOUT, MAX_TIMEOUT); - memoryTextComplex = TextComplex.builder() - .composite(group) + memoryTextComplex = TextComplex.builder(group, + bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_MEMORY).observe(dataModel.getFunctionConfigPageDataModel())) .addValidator(new RangeValidator(memoryErrMsg, MIN_MEMORY, MAX_MEMORY)) .createLabel(true) - .dataBindingContext(bindingContext) .defaultValue(Integer.toString(DEFAULT_MEMORY)) .labelColSpan(1) .labelValue("Memory (MB):") - .pojoObservableValue(PojoObservables.observeValue( - dataModel.getFunctionConfigPageDataModel(), FunctionConfigPageDataModel.P_MEMORY)) .textColSpan(2) .build(); - timeoutTextComplex = TextComplex.builder() - .composite(group) + timeoutTextComplex = TextComplex.builder(group, + bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_TIMEOUT).observe(dataModel.getFunctionConfigPageDataModel())) .addValidator(new RangeValidator(timeoutErrMsg, MIN_TIMEOUT, MAX_TIMEOUT)) .createLabel(true) - .dataBindingContext(bindingContext) .defaultValue(Integer.toString(DEFAULT_TIMEOUT)) .labelColSpan(1) .labelValue("Timeout (s):") - .pojoObservableValue(PojoObservables.observeValue( - dataModel.getFunctionConfigPageDataModel(), FunctionConfigPageDataModel.P_TIMEOUT)) .textColSpan(2) .build(); } diff --git a/pom.xml b/pom.xml index 87d0987d..69a5179f 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 1.0.0 UTF-8 mars - 1.11.130 + 1.11.248 1.8 1.8 diff --git a/tests/com.amazonaws.eclipse.core.tests/src/com/amazonaws/eclipse/core/regions/RegionUtilsTest.java b/tests/com.amazonaws.eclipse.core.tests/src/com/amazonaws/eclipse/core/regions/RegionUtilsTest.java index ca420a62..fb5990ca 100644 --- a/tests/com.amazonaws.eclipse.core.tests/src/com/amazonaws/eclipse/core/regions/RegionUtilsTest.java +++ b/tests/com.amazonaws.eclipse.core.tests/src/com/amazonaws/eclipse/core/regions/RegionUtilsTest.java @@ -1,5 +1,6 @@ package com.amazonaws.eclipse.core.regions; +import java.io.IOException; import java.util.List; import java.util.regex.Pattern; @@ -16,20 +17,20 @@ public class RegionUtilsTest { // regular region endpoints Pattern.compile("^(http|https)://\\w+\\.(\\w+\\.)?(ca|us|eu|ap|sa)-(gov-)?(east|west|south|north|central|northeast|southeast)-(1|2)\\.amazonaws\\.com(/)?$"), // China region endpoints, currently we only have cn-north-1 region - Pattern.compile("^(http|https)://\\w+\\.(\\w+\\.)?cn-north-1.amazonaws.com.cn(/)?$"), + Pattern.compile("^(http|https)://\\w+\\.(\\w+\\.)?cn-(north|northwest)-1.amazonaws.com.cn(/)?$"), // us-gov region endpoints Pattern.compile("^(http|https)://\\w+\\.us-gov(-west-1)?.amazonaws.com(/)?$") }; @Test - public void testRemoteRegionFile() { + public void testRemoteRegionFile() throws IOException { List regions = RegionUtils.loadRegionsFromS3(); assertRegionEndpointsValid(regions); } @Test - public void testLocalRegionFile() { - List regions = RegionUtils.loadRegionsFromLocalFile(); + public void testLocalRegionFile() throws IOException { + List regions = RegionUtils.loadRegionsFromLocalRegionFile(); assertRegionEndpointsValid(regions); } diff --git a/tests/com.amazonaws.eclipse.lambda.tests/.classpath b/tests/com.amazonaws.eclipse.lambda.tests/.classpath index 625a2791..b6adeb1e 100644 --- a/tests/com.amazonaws.eclipse.lambda.tests/.classpath +++ b/tests/com.amazonaws.eclipse.lambda.tests/.classpath @@ -1,6 +1,6 @@ - + diff --git a/tests/com.amazonaws.eclipse.lambda.tests/.settings/org.eclipse.jdt.core.prefs b/tests/com.amazonaws.eclipse.lambda.tests/.settings/org.eclipse.jdt.core.prefs index f42de363..0c68a61d 100644 --- a/tests/com.amazonaws.eclipse.lambda.tests/.settings/org.eclipse.jdt.core.prefs +++ b/tests/com.amazonaws.eclipse.lambda.tests/.settings/org.eclipse.jdt.core.prefs @@ -1,7 +1,7 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/tests/com.amazonaws.eclipse.lambda.tests/META-INF/MANIFEST.MF b/tests/com.amazonaws.eclipse.lambda.tests/META-INF/MANIFEST.MF index 1a7b9c30..0477ab04 100644 --- a/tests/com.amazonaws.eclipse.lambda.tests/META-INF/MANIFEST.MF +++ b/tests/com.amazonaws.eclipse.lambda.tests/META-INF/MANIFEST.MF @@ -10,4 +10,5 @@ Import-Package: com.fasterxml.jackson.core, com.fasterxml.jackson.databind, org.junit Require-Bundle: com.amazonaws.eclipse.lambda;bundle-version="1.0.0", - com.amazonaws.eclipse.core;bundle-version="2.3.1" + com.amazonaws.eclipse.core;bundle-version="2.3.1", + org.eclipse.debug.ui diff --git a/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/blueprint/BlueprintsTest.java b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/blueprint/BlueprintsTest.java index d5e8c292..e39f2dcf 100644 --- a/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/blueprint/BlueprintsTest.java +++ b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/blueprint/BlueprintsTest.java @@ -182,7 +182,7 @@ private void assertSamTemplateValid(Object dataModel, CodeTemplateManager manage try { String content = CodeTemplateManager.processTemplateWithData(samTemplate, dataModel); assertStringNotEmpty(content); - ServerlessModel model = Serverless.load(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + ServerlessModel model = Serverless.loadFromContent(content); Set physicalIds = model.getServerlessFunctions().keySet(); Set pathIds = blueprint.getHandlerTemplatePaths().keySet(); assertEquals(physicalIds, pathIds); diff --git a/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTrackerTest.java b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTrackerTest.java new file mode 100644 index 00000000..70aa7eea --- /dev/null +++ b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/launching/SamLocalConsoleLineTrackerTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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.launching; + +import java.util.regex.Matcher; + +import static com.amazonaws.eclipse.lambda.launching.SamLocalConsoleLineTracker.URL_PATTERN; + +import org.junit.Assert; +import org.junit.Test; + +public class SamLocalConsoleLineTrackerTest { + private TestCase[] testCases = { + new TestCase("foo: http://234.12.21.12:2000", true, "http://234.12.21.12:2000"), + new TestCase("foo: http://234.12.21.12:2000bar", true, "http://234.12.21.12:2000"), + new TestCase("foo: https://234.12.21.12:3000", true, "https://234.12.21.12:3000"), + new TestCase("foo: 234.12.255.12:2012", true, "234.12.255.12:2012"), + new TestCase("foo: localhost:3000", true, "localhost:3000"), + new TestCase("217.0.0.1:3000", true, "217.0.0.1:3000"), + new TestCase("217.0.0.1", false, null), + new TestCase("This is foo", false, null) + }; + + private static class TestCase { + String text; + boolean valid; + String ipAddress; + + private TestCase(String text, boolean valid, String ipAddress) { + this.text = text; + this.valid = valid; + this.ipAddress = ipAddress; + } + } + + @Test + public void testPatterns() { + for (TestCase testCase : testCases) { + doTestPattern(testCase.text, testCase.valid, testCase.ipAddress); + } + } + + private void doTestPattern(String line, boolean match, String matchString) { + Matcher matcher = URL_PATTERN.matcher(line); + Assert.assertEquals(match, matcher.find()); + if (match) { + String actualMatchString = matcher.group(); + Assert.assertEquals(matchString, actualMatchString); + } + } +} diff --git a/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/ServerlessTemplateMapperTest.java b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/ServerlessTemplateMapperTest.java index f20dc23d..cc5b7e82 100644 --- a/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/ServerlessTemplateMapperTest.java +++ b/tests/com.amazonaws.eclipse.lambda.tests/src/com/amazonaws/eclipse/lambda/serverless/ServerlessTemplateMapperTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertArrayEquals; import java.io.IOException; import java.io.InputStream; @@ -23,7 +24,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Consumer; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -36,131 +39,201 @@ public class ServerlessTemplateMapperTest { - private static final String SERVERLESS_TEMPLATE_FILE = "serverless-template.json"; - private ServerlessModel model; - private ServerlessTemplate template; + private static final String SERVERLESS_JSON_TEMPLATE_FILE = "serverless-template.json"; + private static final String SERVERLESS_YAML_TEMPLATE_FILE = "serverless-template.yaml"; + private static final String CODESTAR_YAML_TEMPLATE_FILE = "codestar.template.yml"; + + private ServerlessModel jsonModel; + private ServerlessTemplate jsonTemplate; + + private ServerlessModel yamlModel; + private ServerlessTemplate yamlTemplate; + + private ServerlessModel codestarModel; + private ServerlessTemplate codestarTemplate; @Before public void setUp() throws JsonParseException, JsonMappingException, IOException { - InputStream serverlessTemplateInputStream = ServerlessTemplateMapperTest.class.getResourceAsStream(SERVERLESS_TEMPLATE_FILE); - model = Serverless.load(serverlessTemplateInputStream); - template = Serverless.convert(model); + try (InputStream serverlessTemplateInputStream = ServerlessTemplateMapperTest.class.getResourceAsStream(SERVERLESS_JSON_TEMPLATE_FILE)) { + jsonModel = Serverless.load(serverlessTemplateInputStream); + jsonTemplate = Serverless.convert(jsonModel); + } + + try (InputStream serverlessTemplateInputStream = ServerlessTemplateMapperTest.class.getResourceAsStream(SERVERLESS_YAML_TEMPLATE_FILE)) { + yamlModel = Serverless.load(serverlessTemplateInputStream); + yamlTemplate = Serverless.convert(yamlModel); + } + + try (InputStream serverlessTemplateInputStream = ServerlessTemplateMapperTest.class.getResourceAsStream(CODESTAR_YAML_TEMPLATE_FILE)) { + codestarModel = Serverless.load(serverlessTemplateInputStream); + codestarTemplate = Serverless.convert(codestarModel); + } + } + + @Test + public void testCodeStarTransform() throws IOException { + Assert.assertArrayEquals(new String[]{"AWS::Serverless-2016-10-31", "AWS::CodeStar"}, + codestarTemplate.getTransform().toArray()); } @Test public void testModel_additionalProperties() { - Map additionalProperties = model.getAdditionalProperties(); - testValuePath(additionalProperties, "bar", "foo"); - testValuePath(additionalProperties, "bar", "foo1", "foo"); + Consumer testModel_additionalProperties = (model) -> { + Map additionalProperties = model.getAdditionalProperties(); + testValuePath(additionalProperties, "bar", "foo"); + testValuePath(additionalProperties, "bar", "foo1", "foo"); + }; + + testModel_additionalProperties.accept(jsonModel); + testModel_additionalProperties.accept(yamlModel); } @Test public void testModel_ServerlessFunction() { - Map functions = model.getServerlessFunctions(); + Consumer testModel_ServerlessFunction = (model) -> { + Map functions = jsonModel.getServerlessFunctions(); + ServerlessFunction function = testServerlessFunction(functions, "ServerlessFunction", + "fakeCodeUri", "fakeHandler", "java8", 512, 300, Arrays.asList("Policy1", "Policy2")); - ServerlessFunction function = testServerlessFunction(functions, "ServerlessFunction", - "fakeCodeUri", "fakeHandler", "java8", 512, 300, Arrays.asList("Policy1", "Policy2")); + Map additionalTopLevelProperties = function.getAdditionalTopLevelProperties(); + testValuePath(additionalTopLevelProperties, "bar", "foo"); - Map additionalTopLevelProperties = function.getAdditionalTopLevelProperties(); - testValuePath(additionalTopLevelProperties, "bar", "foo"); + Map additionalProperties = function.getAdditionalProperties(); + assertTrue(additionalProperties.containsKey("Events")); + assertS3EventMatches((Map)additionalProperties.get("Events"), "S3Event", "fakeBucket"); + }; - Map additionalProperties = function.getAdditionalProperties(); - assertTrue(additionalProperties.containsKey("Events")); - assertS3EventMatches((Map)additionalProperties.get("Events"), "S3Event", "fakeBucket"); + testModel_ServerlessFunction.accept(jsonModel); + testModel_ServerlessFunction.accept(yamlModel); } @Test public void testModel_ServerlessFunction2() { - Map functions = model.getServerlessFunctions(); + Consumer testModel_ServerlessFunction2 = (model) -> { + Map functions = jsonModel.getServerlessFunctions(); + + ServerlessFunction function = testServerlessFunction(functions, "ServerlessFunction2", + "fakeCodeUri", "fakeHandler", "fakeRuntime", 100, 100, Collections.emptyList()); - ServerlessFunction function = testServerlessFunction(functions, "ServerlessFunction2", - "fakeCodeUri", "fakeHandler", "fakeRuntime", 100, 100, Collections.emptyList()); + Map additionalTopLevelProperties = function.getAdditionalTopLevelProperties(); + assertTrue(additionalTopLevelProperties.isEmpty()); - Map additionalTopLevelProperties = function.getAdditionalTopLevelProperties(); - assertTrue(additionalTopLevelProperties.isEmpty()); + Map additionalProperties = function.getAdditionalProperties(); - Map additionalProperties = function.getAdditionalProperties(); + testValuePath(additionalProperties, "value1", "Environment", "Variables", "key1"); + testValuePath(additionalProperties, "value2", "Environment", "Variables", "key2"); + }; - testValuePath(additionalProperties, "value1", "Environment", "Variables", "key1"); - testValuePath(additionalProperties, "value2", "Environment", "Variables", "key2"); + testModel_ServerlessFunction2.accept(jsonModel); + testModel_ServerlessFunction2.accept(yamlModel); } @Test public void testModel_additionalResources() { - Map resources = model.getAdditionalResources(); + Consumer testModel_additionalResources = (model) -> { + Map resources = jsonModel.getAdditionalResources(); - assertTrue(resources.containsKey("IamRole")); - TypeProperties tp = resources.get("IamRole"); - assertEquals("AWS::IAM::Role", tp.getType()); + assertTrue(resources.containsKey("IamRole")); + TypeProperties tp = resources.get("IamRole"); + assertEquals("AWS::IAM::Role", tp.getType()); - Map properties = tp.getProperties(); - assertEquals("fakeValue", properties.get("fakeKey")); + Map properties = tp.getProperties(); + assertEquals("fakeValue", properties.get("fakeKey")); - assertTrue(tp.getAdditionalProperties().containsKey("foo")); - assertEquals("bar", tp.getAdditionalProperties().get("foo")); + assertTrue(tp.getAdditionalProperties().containsKey("foo")); + assertEquals("bar", tp.getAdditionalProperties().get("foo")); + }; + + testModel_additionalResources.accept(jsonModel); + testModel_additionalResources.accept(yamlModel); } @Test public void testTemplate_Metadata() { - assertEquals("2010-09-09", template.getAWSTemplateFormatVersion()); - assertEquals(null, template.getDescription()); - assertEquals("AWS::Serverless-2016-10-31", template.getTransform()); + Consumer testTemplate_Metadata = (model) -> { + assertEquals("2010-09-09", jsonTemplate.getAWSTemplateFormatVersion()); + assertEquals(null, jsonTemplate.getDescription()); + assertArrayEquals(new String[]{"AWS::Serverless-2016-10-31"}, jsonTemplate.getTransform().toArray()); + }; + + testTemplate_Metadata.accept(jsonModel); + testTemplate_Metadata.accept(yamlModel); } @Test public void testTemplate_AdditionalProperties() { - Map additionalProperties = template.getAdditionalProperties(); - testValuePath(additionalProperties, "bar", "foo"); - testValuePath(additionalProperties, "bar", "foo1", "foo"); + Consumer testTemplate_AdditionalProperties = (template) -> { + Map additionalProperties = jsonTemplate.getAdditionalProperties(); + testValuePath(additionalProperties, "bar", "foo"); + testValuePath(additionalProperties, "bar", "foo1", "foo"); + }; + + testTemplate_AdditionalProperties.accept(jsonTemplate); + testTemplate_AdditionalProperties.accept(yamlTemplate); } @Test public void testTemplate_ServerlessFunction() { - Map resources = template.getResources(); - TypeProperties resource = testTemplateResource(resources, "ServerlessFunction", "AWS::Serverless::Function"); - - Map additionalProperties = resource.getAdditionalProperties(); - testValuePath(additionalProperties, "bar", "foo"); - - Map properties = resource.getProperties(); - testValuePath(properties, "fakeCodeUri", "CodeUri"); - testValuePath(properties, "fakeHandler", "Handler"); - testValuePath(properties, "S3", "Events", "S3Event", "Type"); - testValuePath(properties, "fakeBucket", "Events", "S3Event", "Properties", "Bucket"); + Consumer testTemplate_ServerlessFunction = (template) -> { + Map resources = jsonTemplate.getResources(); + TypeProperties resource = testTemplateResource(resources, "ServerlessFunction", "AWS::Serverless::Function"); + + Map additionalProperties = resource.getAdditionalProperties(); + testValuePath(additionalProperties, "bar", "foo"); + + Map properties = resource.getProperties(); + testValuePath(properties, "fakeCodeUri", "CodeUri"); + testValuePath(properties, "fakeHandler", "Handler"); + testValuePath(properties, "S3", "Events", "S3Event", "Type"); + testValuePath(properties, "fakeBucket", "Events", "S3Event", "Properties", "Bucket"); + }; + + testTemplate_ServerlessFunction.accept(jsonTemplate); + testTemplate_ServerlessFunction.accept(yamlTemplate); } @Test public void testTemplate_ServerlessFunction2() { - Map resources = template.getResources(); - TypeProperties resource = testTemplateResource(resources, "ServerlessFunction2", "AWS::Serverless::Function"); - - Map additionalProperties = resource.getAdditionalProperties(); - assertTrue(additionalProperties.isEmpty()); - - Map properties = resource.getProperties(); - testValuePath(properties, "fakeCodeUri", "CodeUri"); - testValuePath(properties, "fakeHandler", "Handler"); - testValuePath(properties, "fakeRuntime", "Runtime"); - testValuePath(properties, "fakeFunctionName", "FunctionName"); - testValuePath(properties, new Integer(100), "MemorySize"); - testValuePath(properties, new Integer(100), "Timeout"); - testValuePath(properties, "value1", "Environment", "Variables", "key1"); - testValuePath(properties, "value2", "Environment", "Variables", "key2"); + Consumer testTemplate_ServerlessFunction2 = (template) -> { + Map resources = jsonTemplate.getResources(); + TypeProperties resource = testTemplateResource(resources, "ServerlessFunction2", "AWS::Serverless::Function"); + + Map additionalProperties = resource.getAdditionalProperties(); + assertTrue(additionalProperties.isEmpty()); + + Map properties = resource.getProperties(); + testValuePath(properties, "fakeCodeUri", "CodeUri"); + testValuePath(properties, "fakeHandler", "Handler"); + testValuePath(properties, "fakeRuntime", "Runtime"); + testValuePath(properties, "fakeFunctionName", "FunctionName"); + testValuePath(properties, new Integer(100), "MemorySize"); + testValuePath(properties, new Integer(100), "Timeout"); + testValuePath(properties, "value1", "Environment", "Variables", "key1"); + testValuePath(properties, "value2", "Environment", "Variables", "key2"); + }; + + testTemplate_ServerlessFunction2.accept(jsonTemplate); + testTemplate_ServerlessFunction2.accept(yamlTemplate); } @Test public void testTemplate_IamRole() { - Map resources = template.getResources(); - TypeProperties resource = testTemplateResource(resources, "ServerlessFunction", "AWS::Serverless::Function"); - - Map additionalProperties = resource.getAdditionalProperties(); - testValuePath(additionalProperties, "bar", "foo"); - - Map properties = resource.getProperties(); - testValuePath(properties, "fakeCodeUri", "CodeUri"); - testValuePath(properties, "fakeHandler", "Handler"); - testValuePath(properties, "S3", "Events", "S3Event", "Type"); - testValuePath(properties, "fakeBucket", "Events", "S3Event", "Properties", "Bucket"); + Consumer testTemplate_IamRole = (template) -> { + Map resources = jsonTemplate.getResources(); + TypeProperties resource = testTemplateResource(resources, "ServerlessFunction", "AWS::Serverless::Function"); + + Map additionalProperties = resource.getAdditionalProperties(); + testValuePath(additionalProperties, "bar", "foo"); + + Map properties = resource.getProperties(); + testValuePath(properties, "fakeCodeUri", "CodeUri"); + testValuePath(properties, "fakeHandler", "Handler"); + testValuePath(properties, "S3", "Events", "S3Event", "Type"); + testValuePath(properties, "fakeBucket", "Events", "S3Event", "Properties", "Bucket"); + }; + + testTemplate_IamRole.accept(jsonTemplate); + testTemplate_IamRole.accept(yamlTemplate); } private TypeProperties testTemplateResource(Map resources, String resourceName, String resourceType) { @@ -203,5 +276,4 @@ private ServerlessFunction testServerlessFunction(Map1.0.0-SNAPSHOT com.amazonaws.eclipse.javasdk - 1.11.130 + 1.11.248 bundle @@ -134,6 +134,10 @@ com.fasterxml.jackson.dataformat jackson-dataformat-xml + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + com.fasterxml.jackson.module jackson-module-jaxb-annotations diff --git a/thirdparty/pom.xml b/thirdparty/pom.xml index 13b4f283..0e09f033 100644 --- a/thirdparty/pom.xml +++ b/thirdparty/pom.xml @@ -12,7 +12,7 @@ - 1.11.130 + 1.11.248 2.4 2.6.6 META-INF @@ -41,6 +41,11 @@ jackson-dataformat-xml ${jackson-version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson-version} + com.fasterxml.jackson.module jackson-module-jaxb-annotations