diff --git a/.doc_gen/metadata/scheduler_metadata.yaml b/.doc_gen/metadata/scheduler_metadata.yaml index 655a6d1c41b..ffd4a2b459a 100644 --- a/.doc_gen/metadata/scheduler_metadata.yaml +++ b/.doc_gen/metadata/scheduler_metadata.yaml @@ -4,6 +4,14 @@ scheduler_hello: synopsis: get started using &EVS;. category: Hello languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: + snippet_tags: + - scheduler.javav2.hello.main .NET: versions: - sdk_version: 3 @@ -17,6 +25,14 @@ scheduler_hello: scheduler: {ListSchedules} scheduler_CreateSchedule: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: + snippet_tags: + - scheduler.javav2.create.schedule.main .NET: versions: - sdk_version: 3 @@ -30,6 +46,14 @@ scheduler_CreateSchedule: scheduler: {CreateSchedule} scheduler_CreateScheduleGroup: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: + snippet_tags: + - scheduler.javav2.create.schedule.group.main .NET: versions: - sdk_version: 3 @@ -52,10 +76,26 @@ scheduler_DeleteSchedule: genai: most snippet_tags: - Scheduler.dotnetv3.DeleteSchedule + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: + snippet_tags: + - scheduler.javav2.delete.schedule.main services: scheduler: {DeleteSchedule} scheduler_DeleteScheduleGroup: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: + snippet_tags: + - scheduler.javav2.delete.schedule.group.main .NET: versions: - sdk_version: 3 @@ -79,6 +119,19 @@ scheduler_ScheduledEventsWorkflow: - Clean up resources and delete the stack. category: Scenarios languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/scheduler + excerpts: + - description: Run the workflow. + genai: most + snippet_tags: + - scheduler.javav2.scenario.main + - description: Wrapper for service operations. + genai: most + snippet_tags: + - scheduler.javav2.actions.main .NET: versions: - sdk_version: 3 diff --git a/javav2/example_code/scheduler/.gitignore b/javav2/example_code/scheduler/.gitignore new file mode 100644 index 00000000000..5ff6309b719 --- /dev/null +++ b/javav2/example_code/scheduler/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/javav2/example_code/scheduler/README.md b/javav2/example_code/scheduler/README.md new file mode 100644 index 00000000000..d62e611a256 --- /dev/null +++ b/javav2/example_code/scheduler/README.md @@ -0,0 +1,115 @@ +# EventBridge Scheduler code examples for the SDK for Java 2.x + +## Overview + +Shows how to use the AWS SDK for Java 2.x to work with Amazon EventBridge Scheduler. + + + + +_EventBridge Scheduler allows you to create, run, and manage tasks on a schedule from one central, managed service._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav2` folder. + + + + + +### Get started + +- [Hello EventBridge Scheduler](src/main/java/com/example/eventbrideschedule/HelloScheduler.java#L6) (`ListSchedules`) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreateSchedule](src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java#L104) +- [CreateScheduleGroup](src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java#L70) +- [DeleteSchedule](src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java#L212) +- [DeleteScheduleGroup](src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java#L182) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [Scheduled Events workflow](src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerScenario.java) + + + + + +## Run the examples + +### Instructions + + + + + +#### Hello EventBridge Scheduler + +This example shows you how to get started using EventBridge Scheduler. + + + +#### Scheduled Events workflow + +This example shows you how to do the following: + +- Deploy a CloudFormation stack with required resources. +- Create a EventBridge Scheduler schedule group. +- Create a one-time EventBridge Scheduler schedule with a flexible time window. +- Create a recurring EventBridge Scheduler schedule with a specified rate. +- Delete EventBridge Scheduler the schedule and schedule group. +- Clean up resources and delete the stack. + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `javav2` folder. + + + + + + +## Additional resources + +- [EventBridge Scheduler User Guide](https://docs.aws.amazon.com/scheduler/latest/userguide/intro.html) +- [EventBridge Scheduler API Reference](https://docs.aws.amazon.com/scheduler/latest/apireference/Welcome.html) +- [SDK for Java 2.x EventBridge Scheduler reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/firehose/package-summary.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/javav2/example_code/scheduler/pom.xml b/javav2/example_code/scheduler/pom.xml new file mode 100644 index 00000000000..b4321ce2a48 --- /dev/null +++ b/javav2/example_code/scheduler/pom.xml @@ -0,0 +1,197 @@ + + + 4.0.0 + + org.example + eventbridge_scheduler + 1.0-SNAPSHOT + + + UTF-8 + 17 + 17 + 17 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + IntegrationTest + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + log4j-plugin-processor + + compile + + process-classes + + only + + org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor + + + + + + + + + + + software.amazon.awssdk + bom + 2.26.14 + pom + import + + + org.apache.logging.log4j + log4j-bom + 2.23.1 + pom + import + + + + + + com.amazonaws + aws-java-sdk-s3 + 1.12.756 + + + org.junit.jupiter + junit-jupiter-api + 5.9.2 + test + + + software.amazon.awssdk + cloudformation + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.14.2 + + + com.sun.mail + jakarta.mail + 2.0.1 + + + org.junit.platform + junit-platform-commons + 1.9.2 + + + software.amazon.awssdk.crt + aws-crt + 0.29.25 + + + org.junit.platform + junit-platform-launcher + 1.9.2 + test + + + software.amazon.awssdk + s3 + + + software.amazon.awssdk + cloudformation + + + software.amazon.awssdk + apache-client + + + software.amazon.awssdk + sso + + + software.amazon.awssdk + ssooidc + + + software.amazon.awssdk + secretsmanager + + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + eventbridge + + + software.amazon.awssdk + scheduler + + + software.amazon.awssdk + iam + + + org.apache.logging.log4j + log4j-core + + + org.slf4j + slf4j-api + 2.0.13 + + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.apache.logging.log4j + log4j-1.2-api + + + org.apache.commons + commons-lang3 + 3.12.0 + + + javax.mail + javax.mail-api + 1.6.2 + + + com.google.code.gson + gson + 2.10.1 + + + \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/HelloScheduler.java b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/HelloScheduler.java new file mode 100644 index 00000000000..2184a9a1b33 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/HelloScheduler.java @@ -0,0 +1,54 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.eventbrideschedule; + +// snippet-start:[scheduler.javav2.hello.main] +import software.amazon.awssdk.services.scheduler.SchedulerAsyncClient; +import software.amazon.awssdk.services.scheduler.model.ListSchedulesRequest; +import software.amazon.awssdk.services.scheduler.model.ScheduleSummary; +import software.amazon.awssdk.services.scheduler.paginators.ListSchedulesPublisher; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class HelloScheduler { + + public static void main(String [] args) { + listSchedulesAsync(); + } + + /** + * Lists all the schedules available. + *

+ * This method uses the {@link SchedulerAsyncClient} to make an asynchronous request to + * list all the schedules available. The method uses the {@link ListSchedulesPublisher} + * to fetch the schedules in a paginated manner, and then processes the responses + * asynchronously. + */ + public static void listSchedulesAsync() { + SchedulerAsyncClient schedulerAsyncClient = SchedulerAsyncClient.create(); + + // Build the request to list schedules + ListSchedulesRequest listSchedulesRequest = ListSchedulesRequest.builder().build(); + + // Use the paginator to fetch all schedules asynchronously. + ListSchedulesPublisher paginator = schedulerAsyncClient.listSchedulesPaginator(listSchedulesRequest); + List results = new ArrayList<>(); + + // Subscribe to the paginator to process the response asynchronously + CompletableFuture future = paginator.subscribe(response -> { + response.schedules().forEach(schedule -> { + results.add(schedule); + System.out.printf("Schedule: %s%n", schedule.name()); + }); + }); + + // Wait for the asynchronous operation to complete. + future.join(); + + // After all schedules are fetched, print the total count. + System.out.printf("Total of %d schedule(s) available.%n", results.size()); + } +} +// snippet-end:[scheduler.javav2.hello.main] \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/CloudFormationHelper.java b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/CloudFormationHelper.java new file mode 100644 index 00000000000..fb52c57e567 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/CloudFormationHelper.java @@ -0,0 +1,220 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.eventbrideschedule.scenario; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudformation.CloudFormationAsyncClient; +import software.amazon.awssdk.services.cloudformation.CloudFormationClient; +import software.amazon.awssdk.services.cloudformation.model.Capability; +import software.amazon.awssdk.services.cloudformation.model.CloudFormationException; +import software.amazon.awssdk.services.cloudformation.model.CreateStackRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStackEventsResponse; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse; +import software.amazon.awssdk.services.cloudformation.model.Output; +import software.amazon.awssdk.services.cloudformation.model.Parameter; +import software.amazon.awssdk.services.cloudformation.model.Stack; +import software.amazon.awssdk.services.cloudformation.waiters.CloudFormationAsyncWaiter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CloudFormationHelper { + private static final String CFN_TEMPLATE = "cfn_template.yaml"; + private static final Logger logger = LoggerFactory.getLogger(CloudFormationHelper.class); + + private static CloudFormationAsyncClient cloudFormationClient; + + private static CloudFormationAsyncClient getCloudFormationClient() { + if (cloudFormationClient == null) { + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder() + .maxConcurrency(100) + .connectionTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .build(); + + ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) + .apiCallAttemptTimeout(Duration.ofSeconds(90)) + .retryStrategy(RetryMode.STANDARD) + .build(); + + cloudFormationClient = CloudFormationAsyncClient.builder() + .httpClient(httpClient) + .overrideConfiguration(overrideConfig) + .build(); + } + return cloudFormationClient; + } + + public static void deployCloudFormationStack(String stackName, String email) { + String templateBody; + // Read the CloudFormation template file + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Path filePath = Paths.get(classLoader.getResource(CFN_TEMPLATE).toURI()); + templateBody = Files.readString(filePath); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException("Failed to read CloudFormation template file", e); + } + + CreateStackRequest stackRequest = CreateStackRequest.builder() + .stackName(stackName) + .templateBody(templateBody) + .parameters(Parameter.builder() + .parameterKey("email") + .parameterValue(email) + .build()) + .capabilities(Capability.CAPABILITY_IAM, Capability.CAPABILITY_NAMED_IAM) + .build(); + + getCloudFormationClient().createStack(stackRequest) + .whenComplete((csr, t) -> { + if (csr != null) { + System.out.println("Stack creation requested, ARN is " + csr.stackId()); + try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) { + waiter.waitUntilStackCreateComplete(request -> request.stackName(stackName)) + .whenComplete((dsr, th) -> { + if (th != null) { + System.out.println("Error waiting for stack creation: " + th.getMessage()); + } else { + dsr.matched().response().orElseThrow(() -> new RuntimeException("Failed to deploy")); + System.out.println("Stack created successfully"); + } + }).join(); + } + } else { + System.out.format("Error creating stack: " + t.getMessage(), t); + throw new RuntimeException(t.getCause().getMessage(), t); + } + }).join(); + } + + /** + * Fetches and logs the details of the stack failure by describing stack events. + */ + private static void fetchStackFailureDetails(CloudFormationClient client, String stackName) { + DescribeStackEventsRequest eventsRequest = DescribeStackEventsRequest.builder() + .stackName(stackName) + .build(); + + DescribeStackEventsResponse eventsResponse = client.describeStackEvents(eventsRequest); + + // Log the relevant events, focusing on failures + eventsResponse.stackEvents().forEach(event -> { + if ("ROLLBACK_COMPLETE".equals(event.resourceStatusAsString()) || "ROLLBACK_IN_PROGRESS".equals(event.resourceStatusAsString())) { + logger.error("Stack rollback event: Resource {} - Status {} - Reason {}", + event.resourceType(), event.resourceStatusAsString(), event.resourceStatusReason()); + } + }); + } + + /** + * Fetches and logs the details of the stack failure by describing stack events. + */ + private static void fetchStackFailureDetails(CloudFormationAsyncClient client, String stackName) { + DescribeStackEventsRequest eventsRequest = DescribeStackEventsRequest.builder() + .stackName(stackName) + .build(); + + client.describeStackEvents(eventsRequest).whenComplete((eventsResponse, th) -> { + if (th != null) { + logger.error("Failed to retrieve stack events: {}", th.getMessage(), th); + } else { + // Log the relevant events, focusing on failures + eventsResponse.stackEvents().forEach(event -> { + if ("ROLLBACK_COMPLETE".equals(event.resourceStatusAsString()) || "ROLLBACK_IN_PROGRESS".equals(event.resourceStatusAsString())) { + logger.error("Stack rollback event: Resource {} - Status {} - Reason {}", + event.resourceType(), event.resourceStatusAsString(), event.resourceStatusReason()); + } + }); + } + }).join(); // Ensures we wait for the events to be fetched + } + + + // Check to see if the Stack exists before deploying it + public static Boolean describeStack(String stackName) { + CloudFormationClient client = CloudFormationClient.builder() + .region(Region.US_EAST_1) + .build(); + try { + // Create a DescribeStacksRequest object + DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder() + .stackName(stackName) + .build(); + + // Call the describeStacks method synchronously + DescribeStacksResponse stacksResponse = client.describeStacks(describeStacksRequest); + + // Process the stacks and check if the stack exists + List stacks = stacksResponse.stacks(); + for (Stack myStack : stacks) { + if (myStack.stackName().equals(stackName)) { + return true; + } + } + } catch (CloudFormationException e) { + System.err.println("Error describing CloudFormation stacks: " + e.getMessage()); + } + + return false; + } + + public static void destroyCloudFormationStack(String stackName) { + getCloudFormationClient().deleteStack(b -> b.stackName(stackName)) + .whenComplete((dsr, t) -> { + if (dsr != null) { + System.out.println("Delete stack requested ...."); + try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) { + waiter.waitUntilStackDeleteComplete(request -> request.stackName(stackName)) + .whenComplete((waiterResponse, throwable) -> + System.out.println("Stack deleted successfully.")) + .join(); + } + } else { + System.out.format("Error deleting stack: " + t.getMessage(), t); + throw new RuntimeException(t.getCause().getMessage(), t); + } + }).join(); + } + + public static Map getStackOutputs(String stackName) { + DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder() + .stackName(stackName) + .build(); + + try { + DescribeStacksResponse describeStacksResponse = getCloudFormationClient().describeStacks(describeStacksRequest).join(); + if (describeStacksResponse.stacks().isEmpty()) { + throw new RuntimeException("Stack not found: " + stackName); + } + + Stack stack = describeStacksResponse.stacks().get(0); + Map outputs = new HashMap<>(); + for (Output output : stack.outputs()) { + outputs.put(output.outputKey(), output.outputValue()); + } + + return outputs; + } catch (SdkServiceException e) { + throw new RuntimeException("Failed to get stack outputs for: " + stackName, e); + } + } +} \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java new file mode 100644 index 00000000000..2d965723a03 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerActions.java @@ -0,0 +1,243 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.eventbrideschedule.scenario; + +// snippet-start:[scheduler.javav2.actions.main] + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.scheduler.SchedulerAsyncClient; +import software.amazon.awssdk.services.scheduler.model.ActionAfterCompletion; +import software.amazon.awssdk.services.scheduler.model.ConflictException; +import software.amazon.awssdk.services.scheduler.model.CreateScheduleGroupRequest; +import software.amazon.awssdk.services.scheduler.model.CreateScheduleGroupResponse; +import software.amazon.awssdk.services.scheduler.model.CreateScheduleRequest; +import software.amazon.awssdk.services.scheduler.model.DeleteScheduleGroupRequest; +import software.amazon.awssdk.services.scheduler.model.DeleteScheduleRequest; +import software.amazon.awssdk.services.scheduler.model.DeleteScheduleResponse; +import software.amazon.awssdk.services.scheduler.model.FlexibleTimeWindow; +import software.amazon.awssdk.services.scheduler.model.FlexibleTimeWindowMode; +import software.amazon.awssdk.services.scheduler.model.ResourceNotFoundException; +import software.amazon.awssdk.services.scheduler.model.Target; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.time.Duration; +import java.util.concurrent.CompletionException; + +public class EventbridgeSchedulerActions { + + private static SchedulerAsyncClient schedulerClient; + private static final Logger logger = LoggerFactory.getLogger(EventbridgeSchedulerActions.class); + + public static SchedulerAsyncClient getAsyncClient() { + if (schedulerClient == null) { + /* + The `NettyNioAsyncHttpClient` class is part of the AWS SDK for Java, version 2, + and it is designed to provide a high-performance, asynchronous HTTP client for interacting with AWS services. + It uses the Netty framework to handle the underlying network communication and the Java NIO API to + provide a non-blocking, event-driven approach to HTTP requests and responses. + */ + + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder() + .maxConcurrency(50) // Adjust as needed. + .connectionTimeout(Duration.ofSeconds(60)) // Set the connection timeout. + .readTimeout(Duration.ofSeconds(60)) // Set the read timeout. + .writeTimeout(Duration.ofSeconds(60)) // Set the write timeout. + .build(); + + ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) // Set the overall API call timeout. + .apiCallAttemptTimeout(Duration.ofSeconds(90)) // Set the individual call attempt timeout. + .retryStrategy(RetryMode.STANDARD) + .build(); + + schedulerClient = SchedulerAsyncClient.builder() + .region(Region.US_EAST_1) + .httpClient(httpClient) + .overrideConfiguration(overrideConfig) + .build(); + } + return schedulerClient; + } + + // snippet-start:[scheduler.javav2.create.schedule.group.main] + + /** + * Creates a new schedule group. + * + * @param name the name of the schedule group to be created + * @return a {@link CompletableFuture} representing the asynchronous operation of creating the schedule group + */ + public CompletableFuture createScheduleGroup(String name) { + CreateScheduleGroupRequest request = CreateScheduleGroupRequest.builder() + .name(name) + .build(); + + logger.info("Initiating createScheduleGroup call for group: {}", name); + CompletableFuture futureResponse = getAsyncClient().createScheduleGroup(request); + futureResponse.whenComplete((response, ex) -> { + if (ex != null) { + if (ex instanceof CompletionException && ex.getCause() instanceof ConflictException) { + // Rethrow the ConflictException + throw (ConflictException) ex.getCause(); + } else { + throw new CompletionException("Failed to create schedule group: " + name, ex); + } + } else if (response == null) { + throw new RuntimeException("Failed to create schedule group: response was null"); + } else { + logger.info("Successfully created schedule group '{}': {}", name, response.scheduleGroupArn()); + } + }); + + return futureResponse; + } + // snippet-end:[scheduler.javav2.create.schedule.group.main] + + // snippet-start:[scheduler.javav2.create.schedule.main] + + /** + * Creates a new schedule for a target task. + * + * @param name the name of the schedule + * @param scheduleExpression The schedule expression that defines when the schedule should run. + * @param scheduleGroupName the name of the schedule group to which the schedule belongs + * @param targetArn the Amazon Resource Name (ARN) of the target task + * @param roleArn the ARN of the IAM role to be used for the schedule + * @param input the input data for the target task + * @param deleteAfterCompletion whether to delete the schedule after it's executed + * @param useFlexibleTimeWindow whether to use a flexible time window for the schedule execution + * @return true if the schedule was successfully created, false otherwise + */ + public CompletableFuture createScheduleAsync( + String name, + String scheduleExpression, + String scheduleGroupName, + String targetArn, + String roleArn, + String input, + boolean deleteAfterCompletion, + boolean useFlexibleTimeWindow) { + + int hoursToRun = 1; + int flexibleTimeWindowMinutes = 10; + + Target target = Target.builder() + .arn(targetArn) + .roleArn(roleArn) + .input(input) + .build(); + + FlexibleTimeWindow flexibleTimeWindow = FlexibleTimeWindow.builder() + .mode(useFlexibleTimeWindow + ? FlexibleTimeWindowMode.FLEXIBLE + : FlexibleTimeWindowMode.OFF) + .maximumWindowInMinutes(useFlexibleTimeWindow + ? flexibleTimeWindowMinutes + : null) + .build(); + + Instant startDate = Instant.now(); + Instant endDate = startDate.plus(Duration.ofHours(hoursToRun)); + + CreateScheduleRequest request = CreateScheduleRequest.builder() + .name(name) + .scheduleExpression(scheduleExpression) + .groupName(scheduleGroupName) + .target(target) + .actionAfterCompletion(deleteAfterCompletion + ? ActionAfterCompletion.DELETE + : ActionAfterCompletion.NONE) + .startDate(startDate) + .endDate(endDate) + .flexibleTimeWindow(flexibleTimeWindow) + .build(); + + return getAsyncClient().createSchedule(request) + .thenApply(response -> { + logger.info("Successfully created schedule {} in schedule group {}, The ARN is {} ", name, scheduleGroupName, response.scheduleArn()); + return true; + }) + .whenComplete((result, ex) -> { + if (ex != null) { + if (ex instanceof ConflictException) { + // Handle ConflictException + logger.error("A conflict exception occurred while creating the schedule: {}", ex.getMessage()); + throw new CompletionException("A conflict exception occurred while creating the schedule: " + ex.getMessage(), ex); + } else { + throw new CompletionException("Error creating schedule: " + ex.getMessage(), ex); + } + } + }); + } + // snippet-end:[scheduler.javav2.create.schedule.main] + + // snippet-start:[scheduler.javav2.delete.schedule.group.main] + + /** + * Deletes the specified schedule group. + * + * @param name the name of the schedule group to delete + * @return a {@link CompletableFuture} that completes when the schedule group has been deleted + * @throws CompletionException if an error occurs while deleting the schedule group + */ + public CompletableFuture deleteScheduleGroupAsync(String name) { + DeleteScheduleGroupRequest request = DeleteScheduleGroupRequest.builder() + .name(name) + .build(); + + return getAsyncClient().deleteScheduleGroup(request) + .thenRun(() -> { + logger.info("Successfully deleted schedule group {}", name); + }) + .whenComplete((result, ex) -> { + if (ex != null) { + if (ex instanceof ResourceNotFoundException) { + throw new CompletionException("The resource was not found: " + ex.getMessage(), ex); + } else { + throw new CompletionException("Error deleting schedule group: " + ex.getMessage(), ex); + } + } + }); + } + // snippet-end:[scheduler.javav2.delete.schedule.group.main] + + // snippet-start:[scheduler.javav2.delete.schedule.main] + + /** + * Deletes a schedule with the specified name and group name. + * + * @param name the name of the schedule to be deleted + * @param groupName the group name of the schedule to be deleted + * @return a {@link CompletableFuture} that, when completed, indicates whether the schedule was successfully deleted + * @throws CompletionException if an error occurs while deleting the schedule, except for the case where the schedule is not found + */ + public CompletableFuture deleteScheduleAsync(String name, String groupName) { + DeleteScheduleRequest request = DeleteScheduleRequest.builder() + .name(name) + .groupName(groupName) + .build(); + + CompletableFuture response = getAsyncClient().deleteSchedule(request); + return response.handle((result, ex) -> { + if (ex != null) { + if (ex instanceof ResourceNotFoundException) { + throw new CompletionException("Resource not found while deleting schedule with ID: " + name, ex); + } else { + throw new CompletionException("Failed to delete schedule.", ex); + } + } + logger.info("Successfully deleted schedule with name {}.", name); + return true; + }); + } + // snippet-end:[scheduler.javav2.delete.schedule.main] +} +// snippet-end:[scheduler.javav2.actions.main] \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerScenario.java b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerScenario.java new file mode 100644 index 00000000000..ab6ec0aaf2a --- /dev/null +++ b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/EventbridgeSchedulerScenario.java @@ -0,0 +1,370 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.eventbrideschedule.scenario; + +// snippet-start:[scheduler.javav2.scenario.main] +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.scheduler.model.SchedulerException; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This Java code example performs the following tasks for the Amazon EventBridge Scheduler workflow: + *

+ * 1. Prepare the Application: + * - Prompt the user for an email address to use for the subscription for the SNS topic subscription. + * - Deploy the Cloud Formation template in resources/cfn_template.yaml for resource creation. + * - Store the outputs of the stack into variables for use in the workflow. + * - Create a schedule group for all workflow schedules. + *

+ * 2. Create one-time Schedule: + * - Create a one-time schedule to send an initial event. + * - Use a Flexible Time Window and set the schedule to delete after completion. + * - Wait for the user to receive the event email from SNS. + *

+ * 3. Create a time-based schedule: + * - Prompt the user for how many X times per Y hours a recurring event should be scheduled. + * - Create the scheduled event for X times per hour for Y hours. + * - Wait for the user to receive the event email from SNS. + * - Delete the schedule when the user is finished. + *

+ * 4. Clean up: + * - Prompt the user for y/n answer if they want to destroy the stack and clean up all resources. + * - Delete the schedule group. + * - Destroy the Cloud Formation stack and wait until the stack has been removed. + */ + +public class EventbridgeSchedulerScenario { + + private static final Logger logger = LoggerFactory.getLogger(EventbridgeSchedulerScenario.class); + private static final Scanner scanner = new Scanner(System.in); + private static String STACK_NAME = "workflow-stack-name"; + private static final String scheduleGroupName = "schedules-group"; + + private static String recurringScheduleName = ""; + + private static String oneTimeScheduleName = ""; + + private static final EventbridgeSchedulerActions eventbridgeActions = new EventbridgeSchedulerActions(); + + public static final String DASHES = new String(new char[80]).replace("\0", "-"); + + public static String roleArn = ""; + public static String snsTopicArn = ""; + + public static void main(String[] args) { + logger.info(DASHES); + logger.info("Welcome to the Amazon EventBridge Scheduler Workflow."); + logger.info(""" + Amazon EventBridge Scheduler is a fully managed service that helps you schedule and execute + a wide range of tasks and events in the cloud. It's designed to simplify the process of + scheduling and managing recurring or one-time events, making it easier for developers and + businesses to automate various workflows and processes. + + One of the key features of Amazon EventBridge Scheduler is its ability to schedule events + based on a variety of triggers, including time-based schedules, custom event patterns, or + even integration with other AWS services. For example, you can use EventBridge Scheduler + to schedule a report generation task to run every weekday at 9 AM, or to trigger a + Lambda function when a specific Amazon S3 object is created. + + This flexibility allows you to build complex and dynamic event-driven architectures + that adapt to your business needs. + + Lets get started... + """); + waitForInputToContinue(); + logger.info(DASHES); + + logger.info(DASHES); + logger.info("1. Prepare the application."); + waitForInputToContinue(); + try { + boolean prepareSuccess = prepareApplication(); + logger.info(DASHES); + + if (prepareSuccess) { + logger.info("2. Create one-time schedule."); + logger.info(""" + A one-time schedule in Amazon EventBridge Scheduler is an event trigger that allows + you to schedule a one-time event to run at a specific date and time. This is useful for + executing a specific task or workflow at a predetermined time, without the need for recurring + or complex scheduling. + """); + waitForInputToContinue(); + createOneTimeSchedule(); + logger.info("Do you want to delete the schedule {} (y/n) ?", oneTimeScheduleName); + String ans = scanner.nextLine().trim(); + if (ans.equalsIgnoreCase("y")) { + eventbridgeActions.deleteScheduleAsync(oneTimeScheduleName,scheduleGroupName); + } + logger.info(DASHES); + + logger.info("3. Create a recurring schedule."); + logger.info(""" + A recurring schedule is a feature that allows you to schedule and manage the execution + of your serverless applications or workloads on a recurring basis. For example, + with EventBridge Scheduler, you can create custom schedules for your AWS Lambda functions, + AWS Step Functions, and other supported event sources, enabling you to automate tasks and + workflows without the need for complex infrastructure management. + """); + waitForInputToContinue(); + createRecurringSchedule(); + logger.info("Do you want to delete the schedule {} (y/n) ?", oneTimeScheduleName); + String ans2 = scanner.nextLine().trim(); + if (ans2.equalsIgnoreCase("y")) { + eventbridgeActions.deleteScheduleAsync(recurringScheduleName,scheduleGroupName); + } + logger.info(DASHES); + } + } catch (Exception ex) { + logger.info("There was a problem with the workflow {}, initiating cleanup...", ex.getMessage()); + cleanUp(); + } + + logger.info(DASHES); + logger.info("4. Clean up the resources."); + logger.info("Do you want to delete these AWS resources (y/n) ?"); + String delAns = scanner.nextLine().trim(); + if (delAns.equalsIgnoreCase("y")) { + cleanUp(); + } else { + logger.info("The AWS resources will not be deleted."); + } + logger.info("Amazon EventBridge Scheduler workflow completed."); + logger.info(DASHES); + } + + /** + * Cleans up the resources associated with the EventBridge scheduler. + * If any errors occur during the cleanup process, the corresponding error messages are logged. + */ + public static void cleanUp() { + logger.info("First, delete the schedule group."); + logger.info("When the schedule group is deleted, schedules that are part of that group are deleted."); + waitForInputToContinue(); + try { + eventbridgeActions.deleteScheduleGroupAsync(scheduleGroupName).join(); + + } catch (CompletionException ce) { + Throwable cause = ce.getCause(); + if (cause instanceof SchedulerException schedulerException) { + logger.error("Scheduler error occurred: Error message: {}, Error code {}", + schedulerException.getMessage(), schedulerException.awsErrorDetails().errorCode(), schedulerException); + } else { + logger.error("An unexpected error occurred: {}", cause.getMessage()); + } + return; + } + + logger.info("Destroy the CloudFormation stack"); + waitForInputToContinue(); + CloudFormationHelper.destroyCloudFormationStack(STACK_NAME); + } + + /** + * Prepares the application by creating resources in a CloudFormation stack, including an SNS topic + * that will be subscribed to the EventBridge Scheduler events. The user will need to confirm the subscription + * in order to receive event emails. + * + * @return true if the application preparation was successful, false otherwise + */ + public static boolean prepareApplication() { + logger.info(""" + This example creates resources in a CloudFormation stack, including an SNS topic + that will be subscribed to the EventBridge Scheduler events. + You will need to confirm the subscription in order to receive event emails. + """); + + String emailAddress = promptUserForEmail(); + logger.info("You entered {}", emailAddress); + + logger.info("Do you want to use a custom Stack name (y/n) ?"); + String ans = scanner.nextLine().trim(); + if (ans.equalsIgnoreCase("y")) { + String newStackName = scanner.nextLine(); + logger.info("You entered {} for the new stack name", newStackName); + waitForInputToContinue(); + STACK_NAME = newStackName; + } + + logger.info("Get the roleArn and snsTopicArn values using a Cloudformation template."); + waitForInputToContinue(); + CloudFormationHelper.deployCloudFormationStack(STACK_NAME, emailAddress); + Map stackOutputs = CloudFormationHelper.getStackOutputs(STACK_NAME); + roleArn = stackOutputs.get("RoleARN"); + snsTopicArn = stackOutputs.get("SNStopicARN"); + + logger.info("The roleARN is {}", roleArn); + logger.info("The snsTopicArn is {}", snsTopicArn); + + try { + eventbridgeActions.createScheduleGroup(scheduleGroupName).join(); + logger.info("createScheduleGroupAsync completed successfully."); + + } catch (RuntimeException e) { + logger.error("Error occurred: {} ", e.getMessage()); + return false; + } + logger.info("Application preparation complete."); + return true; + } + + /** + * Waits for the user to enter 'c' followed by to continue the program. + * This method is used to pause the program execution and wait for user input before + * proceeding. + */ + private static void waitForInputToContinue() { + while (true) { + logger.info(""); + logger.info("Enter 'c' followed by to continue:"); + String input = scanner.nextLine(); + + if (input.trim().equalsIgnoreCase("c")) { + logger.info("Continuing with the program..."); + logger.info(""); + break; + } else { + // Handle invalid input. + logger.info("Invalid input. Please try again."); + } + } + } + + /** + * Prompts the user to enter an email address and validates the input. + * If the provided email address is invalid, the method will prompt the user to try again. + * + * @return the valid email address entered by the user + */ + private static String promptUserForEmail() { + logger.info("Enter an email address to use for event subscriptions: "); + String email = scanner.nextLine(); + if (!isValidEmail(email)) { + logger.info("Invalid email address. Please try again."); + return promptUserForEmail(); + } + return email; + } + + /** + * Checks if the given email address is valid. + * + * @param email the email address to be validated + * @return {@code true} if the email address is valid, {@code false} otherwise + */ + private static boolean isValidEmail(String email) { + try { + InternetAddress emailAddress = new InternetAddress(email); + emailAddress.validate(); + return true; + + } catch (AddressException e) { + return false; + } + } + + /** + * Creates a one-time schedule to send an initial event in 1 minute with a flexible time window. + * + * @return {@code true} if the schedule was created successfully, {@code false} otherwise + */ + public static Boolean createOneTimeSchedule() { + oneTimeScheduleName = promptUserForResourceName("Enter a name for the one-time schedule:"); + logger.info("Creating a one-time schedule named {} to send an initial event in 1 minute with a flexible time window...", oneTimeScheduleName); + LocalDateTime scheduledTime = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + String scheduleExpression = "at(" + scheduledTime.format(formatter) + ")"; + return eventbridgeActions.createScheduleAsync( + oneTimeScheduleName, + scheduleExpression, + scheduleGroupName, + snsTopicArn, + roleArn, + "One time scheduled event test from schedule", + true, + true).join(); + } + + + /** + * Creates a recurring schedule to send events based on a specific time. + * + * @return A {@link CompletableFuture} that completes with a boolean value indicating the success or failure of the operation. + */ + public static Boolean createRecurringSchedule() { + logger.info("Creating a recurring schedule to send events for one hour..."); + recurringScheduleName = promptUserForResourceName("Enter a name for the recurring schedule:"); + + // Prompt the user for the schedule rate (in minutes). + int scheduleRateInMinutes = promptUserForInteger("Enter the desired schedule rate (in minutes): "); + String scheduleExpression = "rate(" + scheduleRateInMinutes + " minutes)"; + return eventbridgeActions.createScheduleAsync( + recurringScheduleName, + scheduleExpression, + scheduleGroupName, + snsTopicArn, + roleArn, + "Recurrent event test from schedule " + recurringScheduleName, + true, + true).join(); + } + + /** + * Prompts the user for a resource name and validates the input. + * + * @param prompt the message to display to the user when prompting for the resource name + * @return the valid resource name entered by the user + */ + private static String promptUserForResourceName(String prompt) { + logger.info(prompt); + String resourceName = scanner.nextLine(); + String regex = "[0-9a-zA-Z-_.]+"; + if (!resourceName.matches(regex)) { + logger.info("Invalid resource name. Please use a name that matches the pattern " + regex + "."); + return promptUserForResourceName(prompt); + } + return resourceName; + } + + /** + * Prompts the user for an integer input and returns the integer value. + * + * @param prompt the message to be displayed to the user when prompting for input + * @return the integer value entered by the user + */ + private static int promptUserForInteger(String prompt) { + logger.info(prompt); + String stringResponse = scanner.nextLine(); + if (stringResponse == null || stringResponse.trim().isEmpty() || !isInteger(stringResponse)) { + logger.info("Invalid integer."); + return promptUserForInteger(prompt); + } + return Integer.parseInt(stringResponse); + } + + /** + * Checks if the given string represents a valid integer. + * + * @param str the string to be checked + * @return {@code true} if the string represents a valid integer, {@code false} otherwise + */ + private static boolean isInteger(String str) { + try { + Integer.parseInt(str); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} +// snippet-end:[scheduler.javav2.scenario.main] \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/README.md b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/README.md new file mode 100644 index 00000000000..d1d4b0243a4 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/java/com/example/eventbrideschedule/scenario/README.md @@ -0,0 +1,100 @@ +# Amazon EventBridge Scheduler Workflow + +## Overview +This example shows how to use AWS SDKs to work with Amazon EventBridge Scheduler with schedules and schedule groups. The workflow demonstrates how to create and delete one-time and recurring schedules within a schedule group to generate events on a specified target, such as an Amazon Simple Notification Service (Amazon SNS) Topic. + +The target SNS topic and the AWS Identity and Access Management (IAM) role used with the schedules are created as part of an AWS CloudFormation stack that is deployed at the start of the workflow, and deleted when the workflow is complete. + +![Scheduler application structure](../../../../../../../../../../workflows/eventbridge_scheduler/resources/scheduler-workflow.png) + +This workflow demonstrates the following steps and tasks: + +1. **Prepare the Application** + + - Prompts the user for an email address to use for the subscription for the SNS topic. + - Prompts the user for a name for the Cloud Formation stack. + - The user must confirm the email subscription to receive event emails. + - Deploys the Cloud Formation template in resources/cfn_template.yaml for resource creation. + - Stores the outputs of the stack into variables for use in the workflow. + - Creates a schedule group for all workflow schedules. + +2. **Create a one-time Schedule** + + - Creates a one-time schedule to send an initial event. + - Prompts the user for a name for the one-time schedule. + - The user must confirm the email subscription to receive an event email. + - The content of the email should include the name of the newly created schedule. + - Use a Flexible Time Window of 10 minutes and set the schedule to delete after completion. + +3. **Create a time-based schedule** + + - Prompts the user for a rate per minutes (example: every 2 minutes) for a scheduled recurring event. + - Creates the scheduled event for X times per hour for 1 hour. + - Deletes the schedule when the user is finished. + - Prompts the user to confirm when they are ready to delete the schedule. + +4. **Clean up** + + - Prompts the user to confirm they want to destroy the stack and clean up all resources. + - Deletes the schedule group. + - Destroys the Cloud Formation stack and wait until the stack has been removed. + +## ⚠ Important + +* Running this code might result in charges to your AWS account. +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + +## Scenario + +### Prerequisites + +Before running this workflow, ensure you have: + +- An AWS account with proper permissions to use Amazon EventBridge Scheduler and Amazon EventBridge. + +### AWS Services Used + +This workflow uses the following AWS services: + +- Amazon EventBridge Scheduler +- Amazon EventBridge +- Amazon Simple Notification Service (SNS) +- AWS CloudFormation + +### Resources + +The workflow scenario deploys the AWS CloudFormation stack with the required resources. + +### Instructions + +After the example compiles, you can run it from the command line. To do so, navigate to +the folder that contains the .sln file and run the following command: + +``` +dotnet run +``` + +Alternatively, you can run the example from within your IDE. + +This starts an interactive scenario that walks you through creating different types of schedules. + +## Amazon EventBridge Scheduler Actions + +The workflow covers the following EventBridge Scheduler API actions: + +- [`CreateSchedule`](https://docs.aws.amazon.com/scheduler/latest/APIReference/API_CreateSchedule.html) +- [`CreateScheduleGroup`](https://docs.aws.amazon.com/scheduler/latest/APIReference/API_CreateScheduleGroup.html) +- [`DeleteSchedule`](https://docs.aws.amazon.com/scheduler/latest/APIReference/API_DeleteSchedule.html) +- [`DeleteScheduleGroup`](https://docs.aws.amazon.com/scheduler/latest/APIReference/API_DeleteScheduleGroup.html) + +## Additional resources + +* [EventBridge Scheduler User Guide](https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html) +* [EventBridge Scheduler API Reference](https://docs.aws.amazon.com/scheduler/latest/APIReference/Welcome.html) + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 diff --git a/javav2/example_code/scheduler/src/main/resources/cfn_template.yaml b/javav2/example_code/scheduler/src/main/resources/cfn_template.yaml new file mode 100644 index 00000000000..030c419d219 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/resources/cfn_template.yaml @@ -0,0 +1,50 @@ +Parameters: + email: + Type: String + Default: 'scheduler_test@example.com' + +Resources: + SchedulerSnsTopic: + Type: AWS::SNS::Topic + Properties: + KmsMasterKeyId: alias/aws/sns + + MySubscription: + Type: AWS::SNS::Subscription + Properties: + Endpoint: !Ref email + Protocol: email + TopicArn: !Ref SchedulerSnsTopic + + SchedulerRole: + Type: AWS::IAM::Role + Properties: + RoleName: example_scheduler_role23 + Path: / + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Principal: + Service: 'scheduler.amazonaws.com' + Action: + - 'sts:AssumeRole' + Policies: + - PolicyName: 'Scheduler_SNS_policy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Action: + - 'sns:Publish' + Resource: !Ref SchedulerSnsTopic + +Outputs: + + SNStopicARN: + Description: SNS topic ARN + Value: !Ref SchedulerSnsTopic + + RoleARN: + Description: Scheduler role ARN + Value: !GetAtt SchedulerRole.Arn diff --git a/javav2/example_code/scheduler/src/main/resources/log4j2.xml b/javav2/example_code/scheduler/src/main/resources/log4j2.xml new file mode 100644 index 00000000000..55f8502ed66 --- /dev/null +++ b/javav2/example_code/scheduler/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/javav2/example_code/scheduler/src/test/java/SchedulerTest.java b/javav2/example_code/scheduler/src/test/java/SchedulerTest.java new file mode 100644 index 00000000000..05f9ae74d18 --- /dev/null +++ b/javav2/example_code/scheduler/src/test/java/SchedulerTest.java @@ -0,0 +1,132 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import com.example.eventbrideschedule.scenario.CloudFormationHelper; +import com.example.eventbrideschedule.scenario.EventbridgeSchedulerActions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class SchedulerTest { + + private static final String STACK_NAME = "workflow-stack-name23"; + + private static String emailAddress = "foo@example.com"; + + private static String scheduleGroupName = "myScheduleGroup"; + private static String roleArn = ""; + private static String snsTopicArn = ""; + + private static String oneTimeScheduleName = "testOneTime1"; + + private static String recurringScheduleName = "recurringSchedule1"; + + private static final EventbridgeSchedulerActions eventbridgeActions = new EventbridgeSchedulerActions(); + + @BeforeAll + public static void setUp() { + CloudFormationHelper.deployCloudFormationStack(STACK_NAME, emailAddress); + Map stackOutputs = CloudFormationHelper.getStackOutputs(STACK_NAME); + roleArn = stackOutputs.get("RoleARN"); + snsTopicArn = stackOutputs.get("SNStopicARN"); + } + + @Test + @Tag("IntegrationTest") + @Order(1) + public void testCreateScheduleGroup() { + assertDoesNotThrow(() -> { + eventbridgeActions.createScheduleGroup(scheduleGroupName).join(); + }); + } + + @Test + @Tag("IntegrationTest") + @Order(2) + public void testOneTimeSchedule() { + assertDoesNotThrow(() -> { + LocalDateTime scheduledTime = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + String scheduleExpression = "at(" + scheduledTime.format(formatter) + ")"; + eventbridgeActions.createScheduleAsync( + oneTimeScheduleName, + scheduleExpression, + scheduleGroupName, + snsTopicArn, + roleArn, + "One time scheduled event test from schedule", + true, + true).join(); + }); + } + + @Test + @Tag("IntegrationTest") + @Order(3) + public void testReoccuringSchedule() { + assertDoesNotThrow(() -> { + int scheduleRateInMinutes = 10; + String scheduleExpression = "rate(" + scheduleRateInMinutes + " minutes)"; + return eventbridgeActions.createScheduleAsync( + recurringScheduleName, + scheduleExpression, + scheduleGroupName, + snsTopicArn, + roleArn, + "Recurrent event test from schedule " + recurringScheduleName, + true, + true).join(); + }); + } + + @Test + @Tag("IntegrationTest") + @Order(4) + public void testDeleteScheduleGroup() { + assertDoesNotThrow(() -> { + eventbridgeActions.deleteScheduleGroupAsync(scheduleGroupName).join(); + + }); + } + + @Test + @Tag("IntegrationTest") + @Order(5) + public void testDelOneTimeSchedule() { + assertDoesNotThrow(() -> { + eventbridgeActions.deleteScheduleAsync(oneTimeScheduleName, scheduleGroupName).join(); + + }); + } + + @Test + @Tag("IntegrationTest") + @Order(6) + public void testDelReoccringSchedule() { + assertDoesNotThrow(() -> { + eventbridgeActions.deleteScheduleAsync(recurringScheduleName, scheduleGroupName).join(); + }); + } + + @Test + @Tag("IntegrationTest") + @Order(7) + public void testDelStack() { + assertDoesNotThrow(() -> { + CloudFormationHelper.destroyCloudFormationStack(STACK_NAME); + }); + } +} \ No newline at end of file