Lab: Unicorn Locator

This lab gives you experience with setting up an AWS (Amazon Web Services) Lambda with Java.

Tools

You will use a number of tools to develop this lab.

An important thing to know about IntelliJ is that it is likely to show errors in this lab that are not real errors. This means there may be red text you cannot make go away. See the discussion about IntelliJ showing errors in your code at the bottom of this writeup. IDEs try to give you useful information, but there are times they do not. That is a fact of life for all developers and all IDEs.

AWS Classroom is a training environment for using AWS. It is not a certification course; you would need to take specific courses to become a certified AWS developer. If you would like to explore these other courses, talk to your instructor. Our primary reason to use AWS Classroom is to provide access to their services without requiring students to use a credit card to create their own accounts. Be careful of how you use the resources in AWS Classroom; there are limits, and exceeding those limits results in additional work to complete labs.

Note: AWS Classroom is delivered through a Canvas shell. This is an entirely separate instance of Canvas than the one used at MSOE. Do not use your MSOE password with the AWS Classroom because that could give other parties access to your MSOE account.

You should have received an email telling you how to get into the classroom. If you have not, check your Junk Email folder, and contact your instructor if it is in neither place. When you get this email, create a new Canvas account with AWS. Use your MSOE email address to create this account, just not your MSOE password. If required, set up two-factor authentication through your email address. Once you are in AWS,

  1. Click on the LMS link at the top right of the page (or available through the ≡ menu) to launch the AWS Canvas shell.
  2. Visit the course created by your instructor. It should have a name like AWS Learner Lab. You may have other courses in your list, but this is the easiest one to use for SWE 2410. Ask your instructor if you cannot find the course.
  3. Click on Modules (in the left) and then the module for running AWS. A blue V will appear with a moving graphic. After a minute or so the V will be replaced by another page.
  4. At the top of the page, above the module, you will see a bar with “AWS” and “Start Lab” on it. Click on Start Lab. A small circle will indicate that it is starting up. This will take several minutes; you can skip to the next section (Deploying your first Lambda) for now. Do not click on the AWS link until after the Start Lab is complete.
  5. When Start Lab is complete, click on the “AWS” link in the upper left. Note the text is very small. This starts the AWS Console in a new tab. Depending on your browser settings, you may need to allow popups from AWS for the console to appear.

You should now be seeing the AWS Console with an image similar to

console home

Feel free to explore a bit. Many resources are part of the free tier; you can explore these without any problems. Do talk to your instructor before starting paid resources. But there are many resources that are free for the first year that you can try out on your own.

Warning: Do not hit the ↺ Reset button. It takes up to an hour for a reset to complete, and it is designed for cases where you have a large number of resources to destroy. For this course, you can achieve the same thing by simply deleting any Lambdas you write. If you do hit the reset button, do not hit it multiple times; hitting it restarts the process and means you have to wait even longer before the reset is complete.

Deploying your first Lambda

Building the Jar File

To deploy a Java function as a Lambda, you first create a .jar file with a specific format. Your instructor will give you a starting point for this lab on GitHub Classroom. Use the provided link to clone your repository. The source code is also available in unicorn-locator.zip.

One of the things you notice quickly is the deeply nested nature of the repositories; for example, the handler for this lab is in src/main/java/com/unicorn/location/UnicornPostLocationHandler.java. These layers mean AWS Lambda was developed for working on much larger projects! Don’t worry about all of the levels, but also don’t make any changes to the structure. Most of your changes for this lab will be in UnicornPostLocationHandler.java.

As mentioned above, this project builds using the Maven tool. Maven is similar to other build tools such as Make, CMake, and Ant. A key goal of Maven is to provide a simple, uniform build system, where “simple” means that users need not understand the details of the underlying mechanisms. To run the Maven builder, press the control key twice (quickly) in IntelliJ. This will pop up a “Run Anything” dialog. This dialog allows running commands that are not built in to IntelliJ. For this assignment, enter the command mvn package. Visit the target/ folder; you should see a Jar file called UnicornLocationApi-1.1-jar-with-dependencies.jar. This is your java “lambda” that you will upload to AWS. If you cannot find the file, get help from your instructor.

One reason this might fail is needing to download Java 17. Visit the project structure. If there is no Java 17 option for SDK, use the drop-down menu to Download JDK… and pick a Java 17 distribution. Then re-execute mvn package.

Creating a Lambda on AWS

Return to the AWS website that you opened earlier. If it is not open yet, click on the AWS link in the upper left corner.

As mentioned earlier, AWS has a very large number of services. The simplest way to access them is to search. Click on the search bar at the top of the screen (just under the URL line) and enter “lambda”. Click on the lambda option that appears. Then,

  1. In the top-right of the window, click on “Create function.”
  2. Name your function unicorn. Don’t ask why. Apparently, all amazonians love unicorns.
  3. Select “Java 17” for the runtime (or Java 21 if you built that version).
  4. Open the arrow “Change default execution role”.
  5. Select “use an existing role”
  6. Select the existing role, “LabRole”. Contact your instructor immediately if you do not see this role available.
  7. Click “Create Function”.

AWS is now ready for you to upload your Jar file.

Uploading your .jar to your lambda

  1. Under the Code tab (where you start), and under the Code Source section, on the right, open the Upload from dropdown and select .zip or .jar file.

  2. Within your IntelliJ project window, go into the target folder and find the UnicornLocationApi-1.1-jar-with-dependencies.jar that you built earlier. You can drag and drop this onto the .jar upload window you just opened.

  3. In the Runtime Settings box (below the Code source box), click the “Edit” button on the right side. Set the Handler to

    com.unicorn.location.UnicornPostLocationHandler::handleRequest

Note that this is simply the fully qualified method reference for the handleRequest method. If you build your own IntelliJ project, folders may get compressed. In that case the handler name may be slightly different such as adding main.java at the start:

   main.java.com.unicorn.location.UnicornPostLocationHandler::handleRequest

Test your API

You are now ready to test. Click on the Test tab, then click the orange “Test” button. This test should pass and you should see a green box with the text “Executing function: succeeded”. Contact your instructor immediately if it does not. Clicking on the arrow in the box shows the detailed test response.

Create a gateway

  1. At the top of the Amazon Console, enter api gateway into the search bar. Click on the API Gateway option that pops up.

  2. In the HTTP API option box, click the bright orange Build button. Name your API unicorn-api. Then click on Add integration button and select Lambda. Click on the box for Lambda function and select the unicorn lambda you created earlier. Click on Next.

  3. From the “Configure routes” page, click Add route, select “ANY” for the method, and change the resource path to /add-unicorn-location. The integration target is the lambda you created (unicorn). Finally, click on Next. This sets up an API Endpoint that you can access from the web.

  4. For “Stage name”, leave the entry to $default. Click Next.

  5. On the Review and Create screen, click on Create.

Seeing your lambda live on the open web

On the pane on the left, under Deploy, click on Stages, and select the $default stage. Then, under Invoke URL, click on the URL that will look something like: https://yszfheuqqvc.execute-api.us-east-1.amazonaws.com. Clicking on this brings up a new web page. Edit the URL at the top, adding your route at the end so the browser points to your API endpoint, something like https://yszfhaiqvc.execute-api.us-east-1.amazonaws.com/add-unicorn-location You should get the response from the URL at this point. It should simply say, “Hello, World! I’m a fresh new lambda!”, just like in your Java code you compiled earlier. If you do not, re-check the endpoint name to the end of the URL.

Logging

The following steps will have you modify UnicornPostLocationHandler.java (the only Java source file in the provided code) to add logging. Logging is very useful for debugging your Lambda. Without logging, the only way to track down issues is to return additional information from the Lambda, and that is a very slow way to debug code. Logging gives you the equivalent of System.out.println() at a console.

Adding logging to the lambda

Add the libraries you need for logging to your pom.xml file in the root of your repo:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.12</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>2.0.12</version>
</dependency>

That file contains a <dependencies> tag already. Put the new dependency items between the <dependencies> start tag and the </dependencies> end tag. For brevity, the next time we say to put some new text between a start and end tag, we’ll say to insert it inside the <dependencies> ... </dependencies> tag, meaning the same thing.

Whenever you change the Maven file (pom.xml) from within IntelliJ (the recommended method), Maven should pop up a button prompting you to reload the file. Click on that button to reload the dependencies. If this does not happen, go to IntelliJ’s File menu and select Repair IDE. This forces re-indexing to recognize new classes.

Go to UnicornPostLocationHandler.java and add an instance variable for logging:

private final Logger logger = LoggerFactory.getLogger(UnicornPostLocationHandler.class);

IntelliJ should offer to import the package. If it does not, import Logger and LoggerFactory from org.slf4j.

Update handleRequest to add logging:

public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
    logger.info("Received a request here!");

    return new APIGatewayProxyResponseEvent()
            .withStatusCode(200)
            .withBody("This is supposed to be the Unicorn Location API at some point!");
}

Rebuild by tapping the control key twice and running mvn package again. Upload the new .jar to the lambda as you did before.

Looking at your log results

Retest the lambda through the web interface. It should still pass. At the bottom of the colorful pane telling you about the passed test, look at the Log output section. This section should match the print statements in your program, including any custom messages you slipped into the .info command above.

Sending, returning JSON data

So far, your lambda simply prints the same response no matter what it is given as an input. To make the program respond to input, you will rewrite it to process JSON data. JSON stands for JavaScript Object Notation. In this notation, brackets ([ and ]) are used to mark lists and braces ({ and }) are used to mark objects. Values are comma-separated within the lists and objects. Using standard libraries, we can encode and decode text that is in JSON format. This means we can type simple text (as opposed to creating binary data) to write JSON data inputs.

Your next step is to extend your application to accept JSON data from the user. The provided code uses the Amazon API Gateway Proxy-Integration which embeds some additional information into the JSON received by the app so that we view the headers later, and not just the JSON body that the client sent. Insert the following directives into your pom.xml file (between the <dependency> .. </dependency> tags) to add the Jackson library:

<dependency>
    <groupId>com.fasterxml.jackson.jr</groupId>
    <artifactId>jackson-jr-objects</artifactId>
    <version>2.17.0</version>
</dependency>

Next, create a new file called UnicornLocation.java and paste the following code into it:

package com.unicorn.location;

public class UnicornLocation {

    private String unicornName;
    private String longitude;
    private String latitude;

    public String getUnicornName() {
        return unicornName;
    }

    public void setUnicornName(String unicornName) {
        this.unicornName = unicornName;
    }

    public String getLongitude() {
        return longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }

    public String getLatitude() {
        return latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }
}

The Jackson and Java Beans libraries use this structure to generate and parse JSON objects. Exact names of get and set methods matter; the libraries use these names to determine what the fields for the JSON object, and if there are mismatches the libraries will throw exceptions.

Return to the UnicornPostLocationHandler.java file and update the handler to

public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
    try {
        UnicornLocation unicornLocation = JSON.std.beanFrom(UnicornLocation.class, input.getBody());

        return new APIGatewayProxyResponseEvent()
                .withStatusCode(200)
                .withBody("Received unicorn location: " + JSON.std.asString(unicornLocation));
    } catch (Exception e) {
        logger.error("Error while processing the request", e);
        return new APIGatewayProxyResponseEvent()
                .withStatusCode(400)
                .withBody("Error processing the request");
    }
}

The key bit in this is JSON.std.beanFrom. This is the call to the Beans library to generate an instance of the UnicornLocation class. You could write code to parse the JSON text directly, but that would provide less error checking and generally be less robust. The call to JSON.std (to turn the location object into a JSON object that can be written as a string) uses the Jackson library. Add

import com.fasterxml.jackson.jr.ob.JSON;

to your code.

Now, rebuild and deploy your jar as above. If it does not build, double-check that you have just the Jackson JSON library; delete any imports for other JSON libraries.

Test your code through the AWS interface. The should pass, but it will show an error code 400 because the application is expecting requests from the API Gateway. Return to the $default stage of your API’s gateway, to that URL you first got working that looked something like https://yszfhaiqvc.execute-api.us-east-1.amazonaws.com/add-unicorn-location. Refresh the link. It should show the same 400 error code you saw a moment ago.

Next, download Postman and install it. Launch the app. Note you do not need to create a postman account for this lab. Within Postman, Use POST as the method, set the URL to the gateway URL (such as https://yszvc.execute-api.us-east-1.amazonaws.com/add-unicorn-location), and set the body format to be raw. Enter the body

{
  "unicornName": "Richard",
  "longitude": "13.404954",
  "latitude": "52.520008"
}

Clicking Send should return the response

Received unicorn location: {"latitude":"52.520008","longitude":"13.404954","unicornName":"Richard"}

You now have a lambda that turns JSON into a Java object and back.

Submitting your lab

Be sure to commit and push your code to the assignment repository. Your instructor may have additional submission requirements. See Canvas.

You may need to demonstrate this lab to your instructor. In any case, please do not reset your resources for the lab. All these resources fall within the free tier, so completing this lab is unlikely to use many Amazon credits. Leaving them in place allows your instructor to investigate problems or to grade your solution later. Change the data to use your own name (first and last name) for the unicorn’s name and set the last 4 digits of the lattitude and longitude to the time and minute of your test using a 24-hour clock (such as 1435 for 2:35 pm). This confirms your code is actually working.

If the writeup calls for you to capture a screen shot of your solution, do it with PostMan. Copy the appropriate endpoint into the URL window, set the JSON input to the values described in the previous paragraph, change the action to a GET, and click Send. This is important. It documents whose submission this is, and gives us confidence that your Lambda is running. Do not copy the full window, just the portion of the PostMan window that includes the URL, the Body, and the returned response. We do not need to see your history or other windows. Remember that when we are grading in Canvas, we see your output in a small window, so if you capture your full screen then the text gets compressed to a very small size.

Fixing problems

A collection of issues and possible solutions:

  1. I’m having difficulties getting into AWS Classroom: Start with your email from AWS inviting you to the classroom. You may need to check your junk mail folder. When you click on the button to accept the invitation, it gives you a choice based on having a Canvas account and creating a new account. What they mean to ask is whether you have an AWS Canvas account already, and the answer is most certainly “no”. Remember that the AWS Canvas is completely separate from the MSOE Canvas, so having an account on the Canvas system at MSOE does not mean you have a Canvas account with AWS. Click on the create new account button.

  2. AWS console will not start up:

    • Check the top of your browser page for options to allow the browser to open new windows. AWS Learner Lab opens the console in a new browser window, and most browsers are set to require permission to open the new window. Give it the permission.

    • Consider changing browsers. Browsers that have worked include Firefox, Chrome, and Edge.

    • Ensure you have popups enabled for the AWS Console site. Your browser should prompt you for permission to open the console, but if it does not you may need to research how to allow it.

  3. When I click on the Test button, I get a message indicating it failed: Try clicking on the Amazon Q helper button that appears; it seems this gives useful diagnostics. If that does not help, talk to your instructor.

  4. IntelliJ shows errors in my code: Do not trust IntelliJ to identify errors. It often marks things as incorrect that are perfectly fine, and its corrections for the code in this lab are suspect. Really! IDEs are built on top of various tools, and if the tool’s output changes slightly the IDE often displays incorrect information. “Get rid of red markings” is never the goal; the goal is to get running code. Look for a tiny “m” from Maven; its suggestions are much more helpful. The ultimate answer is to exit IntelliJ, reenter, and rebuild with mvn package. Error messages from mvn are actual errors.

  5. I ran mvn package and still see lots of errors: make sure the pom.xml file is in the top level of the project. Several students found it was moved to the src subfolder, and the mvn command cannot find the file in that case.

  6. In spite of the previous steps, I am still getting errors from mvn package: go to IntelliJ’s File menu and select Repair IDE to force re-indexing so new dependencies are recognized.

References

This lab is derived from a workshop created by Amazon; you can also review its github repository. The lab was written by J. Yoder and B. Lewis and modified by R. Hasker.