Skip to content

Commit

Permalink
Merge 19aeca5 into 93e18fd
Browse files Browse the repository at this point in the history
  • Loading branch information
mbland committed Nov 26, 2023
2 parents 93e18fd + 19aeca5 commit 1981dc3
Show file tree
Hide file tree
Showing 8 changed files with 543 additions and 62 deletions.
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ antJunitVer = "1.10.14"
seleniumVer = "4.15.0"
weldVer = "5.1.2.Final"
jandexVer = "3.1.5"
jacksonVer = "2.16.0"

[libraries.servlet]
module = "jakarta.servlet:jakarta.servlet-api"
Expand Down Expand Up @@ -43,3 +44,7 @@ version.ref = "weldVer"
[libraries.jandex]
module = "io.smallrye:jandex"
version.ref ="jandexVer"

[libraries.jackson]
module = "com.fasterxml.jackson.core:jackson-databind"
version.ref = "jacksonVer"
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
// Apply the foojay-resolver plugin to allow automatic download of JDKs
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
id("com.github.node-gradle.node") version "7.0.1" apply false
id("io.freefair.lombok") version "8.4" apply false
id("com.github.ben-manes.versions") version "0.50.0" apply false
}

Expand Down
2 changes: 2 additions & 0 deletions strcalc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
war
jacoco
id("com.github.node-gradle.node")
id("io.freefair.lombok")
id("com.github.ben-manes.versions")
}

Expand All @@ -21,6 +22,7 @@ val antJUnit: Configuration by configurations.creating

dependencies {
implementation(libs.jandex)
implementation(libs.jackson)
implementation(libs.weld)

testImplementation(libs.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,143 @@

package com.mike_bland.training.testing.stringcalculator;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.IOException;

@WebServlet("/add")
public class Servlet extends HttpServlet {
public static final String DEFAULT_ROOT = "/strcalc";

private final ObjectMapper mapper = new ObjectMapper();
@Inject private StringCalculator calculator;

// No-arg constructor required for Tomcat startup.
//
// After creation, Weld injects the calculator field before Tomcat invokes
// Servlet.init(). See the comment for printMethodAndCalculatorClass()
// below.
public Servlet() {
printMethodAndCalculatorClass(this.calculator);
}

// Allows us to inject different implementations for testing.
//
// The @Inject annotation does nothing with this constructor, because Tomcat
// will create the Servlet using the no-arg constructor before Weld
// injection begins. See the comment for printMethodAndCalculatorClass()
// below.
Servlet(StringCalculator calculator) {
this.calculator = calculator;
printMethodAndCalculatorClass(this.calculator);
}

// Initializes the Servlet after Weld injects dependencies.
//
// See the comment for printMethodAndCalculatorClass() below.
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
printMethodAndCalculatorClass(this.calculator);
}

// Defines the StringCalculator request payload.
@AllArgsConstructor
@NoArgsConstructor
static class CalculatorRequest {
public String numbers;
}

// Defines the StringCalculator response payload.
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(Include.NON_DEFAULT)
static class CalculatorResponse {
public int result;
public String error;
}

// Returns a placeholder string.
//
// Actual StringCalculator /add requests will use the POST method, but this
// GET response can help with learning and troubleshooting. It can be safely
// removed at any time.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().print("placeholder for /add API endpoint");
}

// Satisfies a StringCalculator.add() request.
//
// The request body should contain a JSON CalculatorRequest payload.
//
// If StringCalculator.add() throws, the "error" field of the JSON
// CalculatorResponse payload will contain the exception message.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
var respPayload = new CalculatorResponse();

try (var reqBody = req.getInputStream()) {
var reqPayload = mapper.readValue(reqBody, CalculatorRequest.class);
respPayload.result = calculator.add(reqPayload.numbers);
resp.setStatus(HttpServletResponse.SC_OK);

} catch (StringCalculator.Exception e) {
respPayload.error = e.getMessage();
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
try (var respBody = resp.getOutputStream()) {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
respBody.write(mapper.writeValueAsBytes(respPayload));
}
}

// Used to illustrate how Servlet construction, initialization, and
// dependency injection happens.
//
// When Tomcat launches a servlet (i.e., by loading a WAR file or using
// methods other than Tomcat.addServlet()), we see that only the no-arg
// constructor executes. By the time init() executes, Weld has injected a
// proxy object for the TemporaryStringCalculator implementation:
//
// Servlet.java:NN: Servlet.<init> ()void: null
// Servlet.java:NN: Servlet.init (ServletConfig)void:
// TemporaryStringCalculator$Proxy$_$$_WeldClientProxy
//
// So when it comes to servlet implementations running in production, we
// can't @Inject objects via constructor injection. As I understand it,
// other objects can still use constructor injection, but Servlet
// implementations are a special case.
private static void printMethodAndCalculatorClass(StringCalculator calc) {
var caller = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(s -> s.skip(1).findFirst())
.orElseThrow();

System.out.printf(
"%s:%d: %s.%s %s: %s%n",
caller.getFileName(),
caller.getLineNumber(),
caller.getDeclaringClass().getSimpleName(),
caller.getMethodName(),
caller.getMethodType(),
calc == null ? "null" : calc.getClass().getSimpleName()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mike_bland.training.testing.stringcalculator;

public interface StringCalculator {
class Exception extends java.lang.Exception {
public Exception(String message) {
super(message);
}
}

int add(String numbers) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mike_bland.training.testing.stringcalculator;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class TemporaryStringCalculator implements StringCalculator {
public static class Exception extends StringCalculator.Exception {
public Exception(String numbers) {
super(String.format(
"TemporaryStringCalculator received: \"%s\"", numbers
));
}
}

@Override
public int add(String numbers) throws Exception {
throw new Exception(numbers);
}
}
Loading

0 comments on commit 1981dc3

Please sign in to comment.