Migration from thymeleaf 3.0 to 3.1 - spring

I have a minimalistic thymeleaf config in my spring boot app, which is used just to render the html into PDF file.
I am upgrading to spring boot 3, and need to upgrade thymeleaf from 3.0 to 3.1
Here is my old code before upgrade to spring boot 3, the main difference is the construction of WebContext. In thymeleaf 3.0 I was building it based on servlet request/response, but in thymeleaf 3.1, we are building it from WebExchange object
Controller for Thymeleaf 3.0
override fun previewDataController(
request: HttpServletRequest,
response: HttpServletResponse,
objId: UUID,
): ResponseEntity<ByteArray> {
val context = WebContext(request, response, request.servletContext)
val pdfData = someService.previewData(context, objId)
...
}
And the upgrade to Thymeleaf 3.1
override fun previewDataController(
webExchange: IWebExchange,
objId: UUID,
): ResponseEntity<ByteArray> {
val context = WebContext(webExchange, webExchange.locale)
val pdfData = someService.previewData(context, objId)
...
}
Here is the failing code
#Autowired
val templateEngine: TemplateEngine
...
fun someMethod(context: WebContext) {
val htmlString = templateEngine.process("resources/template", context)
...
}
I have the object object in the context, and I can see in debug that this property is not null
Here is the template:
<body>
<p>Name <span th:text="${object.name}">#</span></p>
</body>
But my unit test is failing, stating that object from the template is null. Here is the stack trace:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
Questions:
Is this the correct way to build the IWebExchange object? Will it be bound by spring?
Am I missing any configuration for the new 3.1 version?
Can it be a problem of the test context config?
Thanks!

Things have changed considerably for Thymeleaf. Mostly because the JavaEE and JakartaEE support it provides, so things needed to be abstracted away. Simply creating a WebExchange is a bit more involved now.
What you should be able to do (Java code which you can probably convert).
public class ThymeleafHelper {
public static WebContext createContext(HttpServletRequest req, HttpServletResponse res) {
var application = JakartaServletWebApplication.buildApplication(req.getServletContext());
var exchange application.buildExchange(req, res);
return new WebContext(exchange);
}
}
You should be able to call this (and convert it to Kotlin) to create the WebContext you will need.

Related

JUnit tests in Spring Boot 3.0 does not work

Code
GET method inside controller class:
#GetMapping("/getAllRecommendedMovies")
public ResponseEntity<BookMyTicket> getAllRecommendedMovies(
#RequestParam(value = "theatreName", required = false) String theatreName,
#RequestParam(value = "pincode", required = false) Integer pincode,
HttpServletRequest request) {
return Observation.createNotStarted(
request.getRequestURI().substring(1),
observationRegistry
).observe(() -> new ResponseEntity<(
theatreManagementService.getAllRecommendedMovies(theatreName, pincode),
HttpStatus.OK
));
}
JUnit test:
#Test
public void getAllRecommendedMovies() throws Exception {
try (MockedStatic<Observation> utilities = Mockito.mockStatic(Observation.class)) {
utilities.when(
() -> Observation.createNotStarted(Mockito.eq("getAllRecommendedMovies"), Mockito.any())
).thenReturn(Observation.NOOP);
}
mockMvc.perform(get("/getAllRecommendedMovies")).andExpect(status().isOk());
}
Also on Github: TheatreManagementControllerTest.java
Question
I have implemented JUnit test for ObservationRegistry.
Is there any alternate method to implement?
Mockito.mockStatic can only mock static calls that happen in the same thread. See https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks.
Spring MVC tests run the Spring application in its own thread so static mocking with Mockito won't help here.
I suggest you introduce a ObservationService interface to wrap methods like Observation.createNotStarted(..) and use that service in your controller. The service can then be easily mocked using standard Spring testing mechanisms like #MockBean.

How can I test logs of Spring Boot application?

I have an application that is a mix of Spring Boot, Jersey, and Camel applications. It starts as a Spring Boot app. I am writing integration tests, and I need to make asserts on logs?
For instance, I need to assert that the Camel route read a message from source A. How can I make reliable asserts on logs? Is there any industry standard for this?
NOTE: I tried finding any solution, but at the moment, I neither understand how to solve it nor can find ready solutions.
UPDATE 1: The detail that I underestimated, but it seems important. I use Kotlin, NOT Java. I tried applying answer, but it isn't one to one transferable to Kotlin.
UPDATE 2:
This is a conversion from Java to Kotlin. ListAppender doesn't have enough information to resolve the type in Kotlin.
class LoggerExtension : BeforeEachCallback, AfterEachCallback {
private val listAppender: ListAppender<ILoggingEvent> = ListAppender<ILoggingEvent>()
private val logger: Logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME) as Logger
override fun afterEach(extensionContext: ExtensionContext) {
listAppender.stop()
listAppender.list.clear()
logger.detachAppender(listAppender)
}
override fun beforeEach(extensionContext: ExtensionContext) {
logger.addAppender(listAppender)
listAppender.start()
}
val messages: List<String>
get() = listAppender.list.stream().map { e -> e.getMessage() }.collect(Collectors.toList())
val formattedMessages: List<String>
get() = listAppender.list.stream().map { e -> e.getFormattedMessage() }.collect(Collectors.toList())
}
Kotlin: Not enough information to infer type variable A
Not an error, but I have a feeling that it will fail in runtime:
private val logger: Logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME) as Logger
Spring Boot comes with OutputCapture rule for JUnit 4 and OutputCaptureExtension for JUnit 5, that let you assert on text sent to standard output.
public class MyTest {
#Rule
public OutputCaptureRule output = new OutputCaptureRule();
#Test
public void test() {
// test code
assertThat(output).contains("ok");
}
}

How to use jersey 2.0 guice on grizzly

I want to use Guice + Jersey 2.0 on Grizzly. According to this How to use guice-servlet with Jersey 2.0? discussion there is no direct Guice integration for Jersey2 at present but it can be achieved using HK2 as a bridge. I also checked the sample project in Github https://github.com/piersy/jersey2-guice-example-with-test . This project is implemented using Jetty.
But my problem is to implement it in Grizzly. On Jetty it is used like this
#Inject
public MyApplication(ServiceLocator serviceLocator) {
// Set package to look for resources in
packages("example.jersey");
System.out.println("Registering injectables...");
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(Main.injector);
}
My problem on grizzly is , how to get this serviceLocator object?
Thank you.
I have created the sample here
https://github.com/oleksiys/samples/tree/master/jersey2-guice-example-with-test
The Grizzly initialization code looks like this:
final URI uri = UriBuilder.fromUri("http://127.0.0.1/")
.port(8080).build();
// Create HttpServer
final HttpServer serverLocal = GrizzlyHttpServerFactory.createHttpServer(uri, false);
// Create Web application context
final WebappContext context = new WebappContext("Guice Webapp sample", "");
context.addListener(example.jersey.Main.class);
// Initialize and register Jersey ServletContainer
final ServletRegistration servletRegistration =
context.addServlet("ServletContainer", ServletContainer.class);
servletRegistration.addMapping("/*");
servletRegistration.setInitParameter("javax.ws.rs.Application",
"example.jersey.MyApplication");
// Initialize and register GuiceFilter
final FilterRegistration registration =
context.addFilter("GuiceFilter", GuiceFilter.class);
registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), "/*");
context.deploy(serverLocal);
serverLocal.start();
add dependecy
compile group: "org.glassfish.hk2", name: "guice-bridge", version: "2.4.0"
create feature
public class GuiceFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
ServiceLocator serviceLocator = ServiceLocatorProvider.getServiceLocator(context);
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
Injector injector = Guice.createInjector(new AbstractModule() {
#Override
protected void configure() {
bind(YYY.class).to(ZZZ.class);
}
});
guiceBridge.bridgeGuiceInjector(injector);
return true;
}
}
register feature
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(GuiceFeature.class);

Compatibility of ContainerResponseFilter in jersey 1.17

Can i run my CustomFilter extended with ContainerResponseFilter in jersey1.17.
I am using GrizzlyWebServer. Please suggest . Given below is my sample server code to add the filter.
GrizzlyWebServer webServer = new GrizzlyWebServer(.............);
....
....
ServletAdapter adapter3 = new ServletAdapter();
adapter3.addInitParameter("com.sun.jersey.config.property.packages", "com.motilink.server.services");
adapter3.setContextPath("/");
adapter3.setServletInstance(new ServletContainer());
adapter3.addContextParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, PoweredbyResponseFilter.class.getName());
webServer.addGrizzlyAdapter(adapter3, new String[]{"/"});
...
.....
MY Filter:
#FrontierResponse
#Provider
public class PoweredbyResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
System.out.println("hell");
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
Resource Class:
#NameBinding
#Retention(value = RetentionPolicy.RUNTIME)
public #interface FrontierResponse {
}
#GET
#Produces("text/plain")
#Path("plain")
//#FrontierResponse
public String getMessage() {
System.out.println("hello world called");
return "Hello World";
}
and finally i call it from a browser
http:// localhost:4464/plain
Add the ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS property as a init-param and not as a context-param:
...
adapter3.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, PoweredbyResponseFilter.class.getName());
...
EDIT 1
From your answer it seems that you're actually trying to use Jersey 1.x (1.17) runtime with implemented JAX-RS 2.0 providers (ContainerRequestContext and ContainerResponseContext have been introduced in JAX-RS 2.0 and Jersey 1.x doesn't know how to use them).
So my advice would be - drop all your Jersey 1.17 dependencies and replace them with Jersey 2.x dependencies. Take a look at our helloworld-webapp example (particularly at App class) to see how to create a Grizzly server instance with JAX-RS application.
Note that it is sufficient to add just ServerProperties.PROVIDER_PACKAGES property to init-params and your Resources and Providers (incl. response filters) will be scanned and registered in the application.

Spring 3.1 basic usage of Envirionment and PropertySource

I want to inject property values into the Spring context when it is started.
I am trying to do this using the new Environment and PropertySource features of Spring 3.1.
In the class in which loads the Spring context, I define my own PropertySource class as follows:
private static class CustomPropertySource extends PropertySource<String> {
public CustomPropertySource() {super("custom");}
#Override
public String getProperty(String name) {
if (name.equals("region")) {
return "LONDON";
}
return null;
}
Then, I add this property source to the application context:
ClassPathXmlApplicationContext springIntegrationContext =
new ClassPathXmlApplicationContext("classpath:META-INF/spring/ds-spring-intg-context.xml");
context.getEnvironment().getPropertySources().addLast( new CustomPropertySource());
context.refresh();
context.start();
In one of my beans, I try to access the property value:
#Value("${region}")
public void setRegion(String v){
...
}
bur recieve the folowing error:
java.lang.IllegalArgumentException: Caused by:
java.lang.IllegalArgumentException: Could not resolve placeholder
'region' in string value [${region}]
Any help is greatly appreciated
when you pass an XML file location as a constructor argument to ClassPathXmlApplicationContext(..) it straight away does the context.refresh()/context.start() methods. so by passing in your XML locations, you're essentially doing it all in one pass and the context is already started/loaded by the time you get to call context.getEnvironment().getPropertySources....
try this;
ClassPathXmlApplicationContext springIntegrationContext =
new ClassPathXmlApplicationContext();
context.getEnvironment().getPropertySources().addLast( new CustomPropertySource());
context.setLocations("classpath:META-INF/spring/ds-spring-intg-context.xml");
context.refresh();
it will set your sources, then your xml, then start the app context.

Resources