How to create dynamic content using AWS Lambda?

How to create dynamic content using AWS Lambda?

Hello Everyone! Today we are going to see how you can generate dynamic content in Lamdba. We are going to use Java 8, Apache Maven, AWS SDK for Java, Apache Velocity to achieve this.

Uses Cases

There are several scenarios where you want to create dynamic content. For example, to create an HTML page response for an API method instead of returning just data in JSON/ XML. The other scenario is to generate a file or message with dynamic content. For instance, create welcome letters for your customers.

Let's Dive In

We are going to create welcome letters for our credit card customers. The letter's template is :


Hello ${name},

Say hi to your new credit card!
Your credit card number is ${creditCardNumber}.

Regards,
Your Credit Company

Lambda would populate name and credit card number. The generated file is uploaded to S3 for further processing like printing and mailing.

STEP 1:

Create an empty maven project and update pom to the following contents:


<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.rajanpanchal</groupId>
    <artifactId>lambda-java</artifactId>
    <version>1</version>
    <name>Dynamic-content-Lambda</name>
    <description>Generate Dynamic content in Lambda</description>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <aws.java.sdk.version>2.14.11</aws.java.sdk.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>bom</artifactId>
                <version>${aws.java.sdk.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
            <version>1.11.865</version>
        </dependency>

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-parent</artifactId>
            <version>2.2</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-tools</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

We are adding AWS dependencies and apache velocity dependencies. The maven-shade-plugin to create a fat jar.

STEP 2:

We will have our template, discussed earlier, in the resources folder (src/main/resources) with the name welcomeLetter.vm.

STEP 3:

Create a class file with the following content:

package net.rajanpanchal.handlers;

import java.io.File;
import java.io.FileWriter;
import java.util.Map;
import java.util.Properties;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;

public class WelcomeLetterGeneratorHandler implements RequestHandler<Map<String, String>, String> {
    Regions clientRegion = Regions.US_EAST_1;
    String fileObjKeyName = "welcome_letter.txt";
    String bucketName = "welcomelettersbucket";
    static VelocityContext vContext;
    static Template t;
    AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withRegion(clientRegion).build();
    static {
        try {
            // Create a new Velocity Engine
            VelocityEngine velocityEngine = new VelocityEngine();
            // Set properties that allow reading vm file from classpath.
            Properties p = new Properties();
            velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,class");
            velocityEngine.setProperty("class.resource.loader.class",
                    "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            velocityEngine.init(p);
            t = velocityEngine.getTemplate("welcomeLetter.vm");
            vContext = new VelocityContext();

        } catch (Exception e) {
            throw new RuntimeException(e);

        }
    }

    public String handleRequest(Map<String, String> event, Context context) {
        String response = null;
        LambdaLogger logger = context.getLogger();
        try {
            // Add data to velocity context
            vContext.put("name", "Rajan");
            vContext.put("creditCardNumber", "1234 5678 9012 3456");
            File f = new File("/tmp/"+fileObjKeyName);

            FileWriter writer = new FileWriter(f);
            // merge template and Data
            t.merge(vContext, writer);
            writer.flush();
            writer.close();

            // Upload a file as a new object with ContentType and title specified.
            PutObjectRequest request = new PutObjectRequest(bucketName, fileObjKeyName, f);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType("plain/text");
            metadata.addUserMetadata("title", "Welcome Letter");
            request.setMetadata(metadata);
            s3Client.putObject(request);
            response  = new String("Success");
        } catch (Exception ex) {
            response =  new String("Error:"+ex.getMessage());
        }

        return response;
    }
}

In the static block we are creating a velocity engine, setting resource loaders for the engine ('class' and 'file' resource loaders). Then we get the template and create a context to hold data to populate on the template.

In Lambda handler handleRequest, we add data like name and credit card number to the context, and using a FileWriter object we merge the template and the data. Note that we create the file in /tmp/ directory in the Lambda execution environment. Lambda provides some disk space to the function to use. Then we upload this file to the S3 bucket (welcomelettersbucket) with the filename welcome_letter.txt. My bucket exists in US-EAST-1 as indicated by the variable clientRegion.

STEP 4

Now execute maven package command to generate the fat jar with all dependencies. A jar would be generated in the target folder.

STEP 5:

Go to AWS Console, create a Lambda function with runtime Java 8, let it create a default execution role. Upload the jar under Function Code section. Edit Basic Settings and add our handler package and method name

net.rajanpanchal.handlers.WelcomeLetterGeneratorHandler::handleRequest

STEP 6:

Add S3 access policy to the Lambda execution role. Click on Permissions tab - > Execution Role hyperlink. It will take you to the IAM Role. For simplicity, I added AmazonS3FullAccess permission, but you can add fine-grain S3 access.

STEP 7:

Go back to lambda and create Test event. Lambda executed successfully. AWS Lamdba Dynamic Content

Check S3 bucket, the file is saved. AWS Lambda Dynamic Content

AWS Lambda Dynamic Content

The file is ready for further processing!

Source Code:

AWS Lambda dynamic content

Conclusion

It's super easy to create dynamic content in Lambda using Apache Velocity and Java SDK. In addition, using lambda you only pay for what you use. Let me know your thoughts on the article in the comments and how you are going to use this?

If you like the post, feel free to share and follow me on Twitter for updates!