Java 2

Week 5

Topics covered this week:

  • Jakarta EE, Servlets, JSPs
  • Java Standard Tag Library (JSTL)
  • Expression Language

Jakarta Enterprise Edition (EE)

Web Containers

  • Java web applications need an application server (aka a Web Container).
    • The application server runs on a host machine and loads specialized classes (called Servlets) to handle incoming HTTP requests.
  • Several vendors offer open source and commercial Java EE web containers. Notable ones include:
  • For this class, we will use Apache Tomcat as our web container primarily because it is supported by Microsoft Azure, Amazon Web Services, and Heroku.

IntelliJ Ultimate

  • Create a new project in IntelliJ Ultimate.
  • Select "Jakarta EE".
  • Give the project a title and set the project location to your preferred destination.
  • Change the template to "Web application".
  • Select Tomcat 10.1 as the Application server.
  • Select Java and Maven as the Language and Build system.
  • Set the Group name to "edu.kirkwood".
  • Set the Project JDK to JDK 21.
  • Click Next.
  • Select "Jakarta EE 10" as the version.
  • Check "Full Platform" under Specifications.
  • Click "Create".

Explore the project structure

  • IntelliJ IDEA creates a project with some boilerplate code that you can build and deploy successfully.

  • pom.xml is the Project Object Model with Maven configuration information, including dependencies and plugins necessary for building the project.

    • Remind students not to copy pom.xml, only dependencies

  • index.jsp is the starting page of your application that opens when you access the root directory URL. It renders Hello World! and a link to /hello-servlet.

  • HelloServlet.java is the HelloServlet class extends HttpServlet and is annotated with @WebServlet. It processes requests to /hello-servlet: a GET request returns HTML code that renders Hello World!.

Configure Tomcat

  • Click the Tomcat menu arrow and select "Edit Configurations".

  • On the Server tab, change the "On 'Update' action" option to "Restart Server".

    • You must restart the server to recompile the project whenever you change the java source code.

    • This differs from an interpreted language like Python or PHP, which doesn't have to be re-compiled.

  • Change the "On frame deactivation" option to "Update classes and resources".

    • This will update your Java Server Pages (JSPs) without redeploying and restarting the server whenever you switch from IntelliJ to your web browser and refresh the page.

    • Changes to HTML, CSS, and JavaScript don't require recompiling java files.

  • Save the run configuration by clicking OK.

Run Tomcat

  • To run the configuration, click the Run button or press Shift + F10.

  • This run configuration builds a war artifact, starts the Tomcat server, and deploys the artifact to the server. You should see the corresponding output in the Services window.

  • Once this is done, it opens the specified URL in your web browser. The index.jsp file from the webapp folder will automatically display. Click the link to navigate to the /hello-servlet path.

  • In the IntelliJ project notice the new "target" folder that was created. This folder contains the compiled files.

Make Changes

  • Make a change to index.jsp. For example, change the title and h1 element to say "Java 2 Web Demos"

  • Change focus from IntelliJ to the web browser and press the refresh button.

    • The change should be displayed because you changed a front-end resource (a JSP).

  • Click the "Hello Servlet" link.

  • Go back to IntelliJ and make a change to HelloServlet.java. For example, change the message to "My First Servlet!".

  • Change focus from IntelliJ to the web browser and press the refresh button.

    • The change will not display because you changed a back-end resource (a Servlet) that needs to be recompiled.

  • Go back to IntelliJ and click the Run button (or press Shift + F10) to restart the server.

    • The site will redeploy and the changes will be visible.

Troubleshooting

  • Click the Tomcat menu arrow and select "Edit Configurations".

  • If Tomcat Server does not appear, click the Add button, expand the Tomcat Server node, and select Local.

  • Fix any warnings at the bottom of the run configuration settings dialog. Examples include:

    • Warning: No artifacts marked for deployment

      • Likely cause: You cloned the project from GitHub.

      • Solution: Click the Fix button (or click the Deployment tab), Click the add button, select Artifact, select "project-name:war exploded".

      • On the Server tab, be sure the URL is set to:
        http://localhost:8080/project_name_war_exploded/ 

    • Error: Application Server 'Tomcat 10.X.X' is not configured

      • Likely cause: The project was set up on a computer with a different version of Tomcat than your current computer.

      • Solution: Select the correct Application server from the dropdown menu.

Servlets

  • Servlets allow the web application container (Tomcat) to receive HTTP requests from the client and reply with an HTTP response.

  • Servlets contain the functionality needed to implement business logic within the application to process dynamic web page content as the response to the request.

  • In terms of Model-View-Controller (MVC) architecture, Servlets are considered the controller.

  • Any other Java class that does not extend HttpServlet are called "Plain Old Java Objects" (POJOs).

CalculatorServlet

  • Right-click the package that contains HelloServlet and create a new Java Class called MyCalculator.

  • Add the @WebServlet annotation before the class signature. After the annotation, add a parenthesis with a name and value property.
    @WebServlet (name = "myCalculator", value = "/my-calculator")

    The name is typically the class name, written in camel case. The name can be different than the class name.
    • The value can be anything, but it must start with a slash. It is recommended to use all lowercase letters, substitute spaces with dashes, and keep it as short as possible.

      • The value is what will be typed in the browser's address bar.

Extend HttpServlet

  • Servlets are classes that must extend HttpServlet, which inherits from GenericServlet.
    public class MyCalculator extends HttpServlet
  • Servlets must override at least one method, usually one of these:
    • doPost - for HTTP post requests.
      • Use this with form submission to create something new.
    • doGet - for HTTP get requests.
      • Use to retrieve specified resources from data in the URL.
  • Right-click inside the class, choose Generate, choose Override methods. Select doGet. Click OK.
  • Replace super.doGet(req, resp) with this:
    System.out.println("Received a GET request");
  • Run Tomcat. Enter "/my-calculator" in the address bar.
  • View IntelliJ's Service tab to see the output.

Extend HttpServlet

  • Change doGet to doPost. Re-run Tomcat. Enter "/my-calculator" in the address bar.
    • Any HTTP Servlet method you don't override will respond with an HTTP status 405: Method not allowed error.
  • Change doPost back to doGet. Re-run Tomcat.
    • In most cases, all servlets must have a doGet method. Get requests are created when a user visits the URL path.
  • Add a hyperlink in the index.jsp file. Visit that page to see the links.
    • The href attribute must be to the servlet's @WebServlet value.
    • Do not include the slash before the value. If you do, the link will go to http://localhost:8080/ instead of http://localhost:8080/project_name_war_exploded/
    • The page will display HTTP status 404: File not found

Request and Response

  • Notice how doGet() has two parameters: HttpServletRequest and HttpServletResponse.
    • The HttpServletRequest interface provides access to the HTTP protocol-specific properties of a request.
      • This enables you to read raw data from forms, accept files uploaded from a posted form, accept JSON, and much more.
    • The HttpServletResponse interface provides access to the HTTP protocol-specific properties of a response.
      • This enables you to set response headers, write to the response body, redirect the request, set the HTTP status code, send cookies back to the client, and much more.

Java Server Pages (JSPs)

  • Java Server Pages (JSPs) allow us to embed Java content within standard HTML. 
  • In terms of Model-View-Controller (MVC) architecture, JSPs are considered the view, or presentation layer.

  • In many organizations, frontend developers are responsible for the presentation layer while backend developers are responsible for the Java business logic. 
  • In a well-structured application, the presentation layer is separated from the business logic and data persistence layers. Therefore, it is best to use a combination of servlets and JSPs.
  • The benefit is that we can let skilled frontend developers do the HTML/CSS part, and backend developers can concentrate on Java.  This is important because web front-end and back-end development require different skill sets, and not everyone can do all of them.

JSP Comments

  • Right click the webapp folder and create a new JSP file called "my-calculator.jsp".
  • Notice the comment and page directive at the top.
  • JSP comments start and end with <%--  --%>.
  • HTML comments start and end with <!-- -->.
  • HTML comments will appear in the source code, JSP comments will not.
  • JSP comments are better for developers than HTML comments.

JSP Page Directives

  • A directive tag provides instructions for the JSP pre-processor.

  • The tags start with <%@ and end with %>. A page directive provides instructions to the container (Tomcat) that pertain to the current JSP page.

  • The page directive provides you with some controls over how the JSP is translated, rendered, and transmitted back to the client. It can also import a class, include other JSP, or include a JSP tag library.

  • The one created by IntelliJ sets the page's content type and character encoding.

    • The UTF-8 character encoding is important to ensure that HTML displays correctly on all systems in many languages.

  • You may create page directives anywhere in your JSP page, but they are typically coded at the top.

JSP HTML

  • Notice that the rest of the content is HTML.

  • Replace the HTML with the Bootstrap Quickstart code.

  • Change the title to "Calculator App".
    <title>My Calculator</title>

  • Add this to the body section.
    <h1>My Calculator</h1>

  • Because this is a brand new file, you will need to re-run the app.

  • Replace /my-calculator with /my-calculator.jsp. Notice the title that appears.

  • Right-click the page and choose "View page source". Notice that the page directive does not display.

Calculator HTML

  • Replace the h1 element with this code.

  • This form contains two input fields, one for the first number and one for the second number, both using the "text" input type.

    • While there is a "number" input type, I do not want you to use them. We must validate numerical inputs with Java, not HTML.

    • Bootstrap classes are used for styling. Additionally, each input field is placed in a "form-group" div to properly format the labels and inputs using Bootstrap.

<div class="container my-4">
    <div class="row">
        <div class="col-6">
            <h1>My Calculator</h1>
            <p class="lead">Enter two numbers and press submit to calculate the result.</p>
            <form>
                <div class="form-group mb-2">
                    <label for="num1">Number 1:</label>
                    <input type="text" class="form-control" id="num1">
                </div>
                <div class="form-group mb-2">
                    <label for="num2">Number 2:</label>
                    <input type="text" class="form-control" id="num2">
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
    </div>
</div>

form Element

  • Edit the form element to include a method and action attribute.

  • The method will be GET or POST.

  • The action will be the url path to the servlet. If the url path is "/my-calculator" use "my-calculator" for the action.

    <form method="POST" action="my-calculator">

  • If you submit the form, the browser will navigate to /my-calculator, and you will get a 405: Method not allowed error because the servlet currently doesn't handle post requests.

  • Create a doPost method in your MyCalculator servlet. 

    • Replace super.doPost(req, resp) with this:
      System.out.println("Received a POST request");
  • When you submit the form again, the browser will navigate to /my-calculator and will display a blank page. In IntelliJ, the terminal will say "Received a POST request".

input name attributes

  • In the JSP we need to add name attributes to the two input fields.

    <input type="text" ... name="num1">
    <input type="text" ... name="num2">

  • The name attributes are typically the same as the id attributes.

  • Please note that we are using text inputs, not number inputs. In this class, I DO NOT want you to use number inputs. You will be required to validate numbers using Java, not HTML.
    <input type="number" ...>

  • Also, in this class, I DO NOT want you to use required attributes. You will be required to validate required inputs using Java, not HTML.
    <input required ...>

getParameter

  • When the form is submitted using the POST method the doPost method of the Servlet will be called.

  • The parameters num1 and num2 will be sent along with the request.

  • Inside the doPost method we need to get those values by calling the request object's getParameter method. The result return will be a String.

  • Remove or comment out the println statement after you have verified that it works

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String num1 = req.getParameter("num1");
    String num2 = req.getParameter("num2");
    System.out.println(num1 + " " + num2);
}

Helpers Class

  • Create a Validators class in a package called "shared". This class will contain static methods that can be shared between multiple projects.

  • Add a method that will check if a String is a valid number.

public static boolean isANumber(String str) {
    try {
        Double.parseDouble(str);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

Validate the numbers

  • In the doPost method, write if statements to validate the inputs.

  • This code requires the isANumber() method to be imported.
    import static edu.kirkwood.shared.Validators.isANumber;

  • If the inputs are not valid, set an attribute on the request object with an error message.

  • The setAttribute() method always takes two Strings as input.

    • The first is the key, the second is the value.

  • Do not continue the function if there is an error.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String num1 = req.getParameter("num1");
    String num2 = req.getParameter("num2");
    boolean errorFound = false;
    if(!isANumber(num1)) {
        req.setAttribute("num1Error", "Number 1 is not valid");
        errorFound = true;
    }
    if(!isANumber(num2)) {
        req.setAttribute("num2Error", "Number 2 is not valid");
        errorFound = true;
    }
    if(errorFound) {
        req.getRequestDispatcher("my-calculator.jsp").forward(req, resp);
        return; // Do not continue this method
    }
}

doGet method

  • Forward the request and response objects in the doGet method so we can access the form directly by visiting "/my-calculator" instead of "/my-calculator.jsp".

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.getRequestDispatcher("my-calculator.jsp").forward(req, resp);
}

Scriptlets

  • A scriptlet starts with <% and ends with %>.

  • These tags will typically go below the page directive, above the doctype tag.

  • Any Java code can be written between the tags.

  • The getAttribute method returns an Object that needs to be casted to its appropriate value.

  • When using scriptlets, it's important to always check if the values are null. If you don't your program will encounter a 500 server error because of a NullPointerException.

<%
    String num1Error = (String)request.getAttribute("num1Error");
    if(num1Error == null) {
        num1Error = "";
    }
    String num2Error = (String)request.getAttribute("num2Error");
    if(num2Error == null) {
        num2Error = "";
    }
%>

Expressions

  • An expression starts with <%= and ends with %>.

  • These tags will go directly inside HTML.

  • Use these to display the output of a variable set in a scriplet.

  • Remember, if the value is null, this code will encounter a 500 server error.

  • Submitting the form with invalid numbers will display the error messages.

<div class="form-group mb-2">
    <label for="num1">Number 1:</label>
    <input type="text" class="form-control" id="num1" name="num1">
    <div style="color: red;"><%= num1Error %></div>
</div>
<div class="form-group mb-2">
    <label for="num2">Number 2:</label>
    <input type="text" class="form-control" id="num2" name="num2">
    <div style="color: red;"><%= num2Error %></div>
</div>

Previously Entered Values

  • If you enter values in the form, they will not be returned from the servlet. Add code to add those attributes to the request object.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String num1 = req.getParameter("num1");
    String num2 = req.getParameter("num2");
    req.setAttribute("num1", num1);
    req.setAttribute("num2", num2);
    // code omitted
}

Scriptlet and Expressions

  • Add code to the scriptlet to get the num1 and num2 values.

<div class="form-group mb-2">
    <label for="num1">Number 1:</label>
    <input type="text" class="form-control" id="num1" name="num1" value="<%= num1 %>">
    <div style="color: red;"><%= num1Error %></div>
</div>
<div class="form-group mb-2">
    <label for="num2">Number 2:</label>
    <input type="text" class="form-control" id="num2" name="num2" value="<%= num2 %>">
    <div style="color: red;"><%= num2Error %></div>
</div>
  • Add value attributes to the HTML inputs. Use an expression to display the String.

<%
    String num1 = (String)request.getAttribute("num1");
    if(num1 == null) {
        num1 = "";
    }
    String num2 = (String)request.getAttribute("num2");
    if(num2 == null) {
        num2 = "";
    }
    // code omitted
%>

Add the two numbers

  • In the servlet, if there are no errors, call a method to add the two numbers.

  • In this example, I use the BiFunction functional interface to create a lambda expression that returns the sum of two String values.

  • Set the result as a request attribute.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // code omitted
    if(errorFound) {
        req.getRequestDispatcher("my-calculator.jsp").forward(req, resp);
        return; // Do not continue this method
    }
    double sum = getSum.apply(num1, num2); // Call the BiFunction object's abstract method apply
    String result = String.format("%s + %s = %s", num1, num2, sum);
    req.setAttribute("result", result);
    req.getRequestDispatcher("my-calculator.jsp").forward(req, resp);
}

// This lambda expression equivalent to lines 22-26
private static BiFunction<String, String, Double> sum = (num1, num2) -> {
    double n1 = Double.parseDouble(num1);
    double n2 = Double.parseDouble(num2);
    return n1 + n2;
};

/*
private static double getSum(String num1, String num2) {
    double n1 = Double.parseDouble(num1);
    double n2 = Double.parseDouble(num1);
    return n1 + n2;
}
*/

Scriptlet and Expressions

  • Add code to the scriptlet to get the result

<form method="POST" action="my-calculator">
	<!--  Code omitted -->
</form>
<div style="color: green;"><%= result %></div>
  • Use an expression to display the String.

<%
    // code omitted
    String result = (String)request.getAttribute("result");
    if(result == null) {
        result = "";
    }
%>
  • The program will work when you run it.

Helper

  • If you enter 1.1 and 2.2 into the form the result will be something like 3.3000000000005.

  • Create a Helpers class in the shared package. Add code to round a decimal to a specific number of decimal places.

import java.text.DecimalFormat;

public class Helpers {
    public static String round(double number, int numDecPlaces) {
        DecimalFormat decimalFormat = new DecimalFormat("0.#"); // Step 1: Instantiate a DecimalFormat object
        // Step 2: Set the DecimalFormat pattern - 0.# means something will always print to the left of the decimal
        decimalFormat.setMaximumFractionDigits(numDecPlaces); // Step 3: Call the non-static method to set the number of decimal places
        return decimalFormat.format(number); // Step 4: Format the decimal number as a string and return it.
    }
}
  • Call that method when you are assigning the result.

String result = String.format("%s + %s = %s", num1, num2, round(sum, 4));

Java 2 - Week 5

By Marc Hauschildt

Java 2 - Week 5

  • 209