thymeleaf / thymeleaf-spring

Thymeleaf integration module for Spring
http://www.thymeleaf.org
Apache License 2.0
435 stars 157 forks source link

Concatenation with conditional produces error #310

Closed brett53559 closed 1 year ago

brett53559 commented 1 year ago

I'm having an issue where using a th:text method and then using concatenation with a conditional operator is causing the incorrect operation to execute.

Here is the gradle configuration

plugins {
    id 'org.springframework.boot' version '2.6.2'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'org.XYZ'
version = '1.0'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    //Spring stuff
    implementation 'org.springframework.boot:spring-boot-starter-data-ldap:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-data-rest:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-mail:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-security:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-web:2.7.4'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    implementation 'org.springframework.ldap:spring-ldap-core'
    implementation 'org.springframework.security:spring-security-ldap'
    implementation 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.7.4'
    testImplementation 'org.springframework.security:spring-security-test:5.7.3'

    //Spring Cloud
    implementation 'org.springframework.cloud:spring-cloud-starter-config:3.1.4'
    implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap:3.1.4'

    //jackson stuff
    implementation 'com.fasterxml.jackson.core:jackson-core:2.13.4'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4'
    implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.4'
    implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.4'

    //webjars
    implementation 'org.webjars:webjars-locator-core'
    implementation 'org.webjars:sockjs-client:1.5.1'
    implementation 'org.webjars:stomp-websocket:2.3.4'

    //Thymeleaf
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
    implementation 'org.thymeleaf:thymeleaf'

    //Misc
    implementation 'commons-io:commons-io:2.11.0'
    implementation 'mysql:mysql-connector-java:8.0.30'
    implementation 'com.jcraft:jsch:0.1.55'
    implementation 'org.apache.poi:poi-ooxml:5.2.2'
}

test {
    useJUnitPlatform()
}

Here is the ThymeleafTemplateConfiguration class

package org.XYZ.cafeteriaregistration.config;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.StringTemplateResolver;

import java.util.Collections;

@Component
public class ThymeleafTemplateConfiguration {

    @Bean
    public TemplateEngine emailTemplateEngine() {
        final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        // Resolver for TEXT emails
        templateEngine.addTemplateResolver(textTemplateResolver());
        // Resolver for HTML emails (except the editable one)
        templateEngine.addTemplateResolver(htmlTemplateResolver());
        // Resolver for HTML editable emails (which will be treated as a String)
        templateEngine.addTemplateResolver(stringTemplateResolver());
        return templateEngine;
    }

    private ITemplateResolver textTemplateResolver() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setOrder(1);
        templateResolver.setResolvablePatterns(Collections.singleton("text/*"));
        templateResolver.setPrefix("/mail/");
        templateResolver.setSuffix(".txt");
        templateResolver.setTemplateMode(TemplateMode.TEXT);
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setCacheable(false);
        return templateResolver;
    }

    private ITemplateResolver htmlTemplateResolver() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setOrder(2);
        templateResolver.setResolvablePatterns(Collections.singleton("html/*"));
        templateResolver.setPrefix("/mail/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setCacheable(false);
        return templateResolver;
    }

    private ITemplateResolver stringTemplateResolver() {
        final StringTemplateResolver templateResolver = new StringTemplateResolver();
        templateResolver.setOrder(3);
        // No resolvable pattern, will simply process as a String template everything not previously matched
        templateResolver.setTemplateMode("HTML");
        templateResolver.setCacheable(false);
        return templateResolver;
    }

}

Here is my TemplateRenderService class

package org.XYZ.cafeteriaregistration.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.AbstractContext;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.spring5.expression.ThymeleafEvaluationContext;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Service
public class TemplateRenderService {
    private final TemplateEngine templateEngine;
    private final ApplicationContext applicationContext;

    @Autowired
    public TemplateRenderService(TemplateEngine templateEngine,
                                 ApplicationContext applicationContext) {
        this.templateEngine = templateEngine;
        this.applicationContext = applicationContext;
    }

    public String renderFragment(String fileName, String fragmentName, AbstractContext context) {
        Set<String> fragName = new HashSet<>();
        fragName.add(fragmentName);

        String html = templateEngine.process("fragments/" + fileName, fragName, context);
        return trimHtml(html);
    }

    public String renderTemplate(String fileName, AbstractContext context) {
        String html = templateEngine.process(fileName, context);
        return trimHtml(html);
    }

    private String trimHtml(String html) {
        html = html.replace("\r\n", "");
        html = html.replaceAll(">\\s+<", "><");
        return html.trim();
    }

    public WebContext getTemplateContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
        WebContext webContext = new WebContext(request, response, servletContext);
        addSpringContext(webContext);
        return webContext;
    }

    public Context getTemplateContext() {
        //https://stackoverflow.com/questions/31244600/accessing-application-context-beans-from-thymeleaf-email-template
        Context context = new Context();
        addSpringContext(context);
        return context;
    }

    private void addSpringContext(AbstractContext abstractContext) {
        abstractContext.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
                new ThymeleafEvaluationContext(applicationContext, null));
    }
}

Here is the code block where i assign values to the context for fragment rendering

Context context = templateRenderService.getTemplateContext();
                context.setVariable("name", [[String value]]);
                context.setVariable("registration", [[Custom DAO class]]);
                context.setVariable("removeReason", [[String value]]);
                String emailBody = templateRenderService.renderFragment(EMAIL_FRAG_FILE, "headGroupUserRemoved", context);

Here is the code block of the headGroupUserRemoved fragment

<th:block th:fragment="headGroupUserRemoved()">
    <h3>Hello <span th:text="${name}"></span></h3>
    <p>We found that <span th:text="${registration.getUsers().getName()}"></span> had a registration removed for the following reason "<span th:text="${removeReason}"></span>"</p>
    <p>You are receiving this in case you need to log in and fix their registration.</p>
    <p>Here are the details of the registration that was removed:</p>
    <th:block th:include="~{::registrationDetails()}"></th:block>
    <p>If you have any questions or concerns. Please contact the Dietary department.</p>
</th:block>

<th:block th:fragment="registrationDetails()">
    <ul>
        <li th:text="${'Name: ' + registration.getUsers().getName()}"></li>
        <li th:text="${'Date: ' + registration.getScheduleDateAsString()}"></li>
        <li th:text="${'Room: ' + registration.getRooms().getName()}"></li>
        <li th:text="${'Time: ' + registration.getTimeSlots().getStandardTime()}"></li>
        <li th:text="${'Meal: ' + registration.getMealOptions() != null ? registration.getMealOptions().getSignUpName() : 'Own'}"></li>
    </ul>
</th:block>

In this block the "registration.getMealOptions()" IS null. However, it produces the following error info I also tried the reverse "'Meal: ' + registration.getMealOptions() == null ? 'Own' : registration.getMealOptions().getSignUpName()" and it was the same error result.

2023-01-30 13:00:53,807 ERROR org.thymeleaf.TemplateEngine [https-jsse-nio-8443-exec-3] [THYMELEAF][https-jsse-nio-8443-exec-3] Exception processing template "fragments/emailfrags::[headGroupUserRemoved]": An error happened during template parsing (template: "class path resource [templates/fragments/emailfrags.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/fragments/emailfrags.html]")
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100)
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1059)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1053)
    at org.XYZ.cafeteriaregistration.service.TemplateRenderService.renderFragment(TemplateRenderService.java:33)
    at org.XYZ.cafeteriaregistration.service.EmailService.sendEmailToHeadGroup(EmailService.java:179)
    at org.XYZ.cafeteriaregistration.service.EmailService.sendRoomReservationEmail(EmailService.java:98)
    at org.XYZ.cafeteriaregistration.controller.RoomTimeController.addReservation(RoomTimeController.java:191)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter(ResourceUrlEncodingFilter.java:67)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:219)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:132)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:659)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:750)
Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "'Meal: ' + registration.getMealOptions() != null ? registration.getMealOptions().getSignUpName() : 'Own'" (template: "fragments/emailfrags" - line 38, col 13)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
    at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
    ... 103 common frames omitted
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "'Meal: ' + registration.getMealOptions() != null ? registration.getMealOptions().getSignUpName() : 'Own'" (template: "fragments/emailfrags" - line 38, col 13)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:292)
    at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
    at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
    at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
    at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
    at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
    at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1587)
    at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleOpenElementEnd(TemplateHandlerAdapterMarkupHandler.java:304)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:278)
    at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleOpenElementEnd(OutputExpressionInlinePreProcessorHandler.java:186)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:124)
    at org.attoparser.select.BlockSelectorMarkupHandler.handleOpenElementEnd(BlockSelectorMarkupHandler.java:996)
    at org.attoparser.HtmlElement.handleOpenElementEnd(HtmlElement.java:109)
    at org.attoparser.HtmlMarkupHandler.handleOpenElementEnd(HtmlMarkupHandler.java:297)
    at org.attoparser.MarkupEventProcessorHandler.handleOpenElementEnd(MarkupEventProcessorHandler.java:402)
    at org.attoparser.ParsingElementMarkupUtil.parseOpenElement(ParsingElementMarkupUtil.java:159)
    at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:710)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301)
    ... 105 common frames omitted
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method getSignUpName() on null context object
    at org.springframework.expression.spel.ast.MethodReference.throwIfNotNullSafe(MethodReference.java:154)
    at org.springframework.expression.spel.ast.MethodReference.getValueRef(MethodReference.java:83)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:70)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91)
    at org.springframework.expression.spel.ast.Ternary.getValueInternal(Ternary.java:58)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:112)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:337)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:265)
    ... 128 common frames omitted

When I change it to this code

<th:block th:fragment="registrationDetails()">
    <ul>
        <li>Name: <span th:text="${registration.getUsers().getName()}"></span></li>
        <li>Date: <span th:text="${registration.getScheduleDateAsString()}"></span></li>
        <li>Room: <span th:text="${registration.getRooms().getName()}"></span></li>
        <li>Time: <span th:text="${registration.getTimeSlots().getStandardTime()}"></span></li>
        <li>Meal: <span th:text="${registration.getMealOptions() != null ? registration.getMealOptions().getSignUpName() : 'Own'}"></span></li>
    </ul>
</th:block>

There is no issues.

brett53559 commented 1 year ago

This is not an error with Thymeleaf but instead a oversight on my part.

The code should have read

<th:block th:fragment="registrationDetails()">
    <ul>
        <li th:text="${'Name: ' + registration.getUsers().getName()}"></li>
        <li th:text="${'Date: ' + registration.getScheduleDateAsString()}"></li>
        <li th:text="${'Room: ' + registration.getRooms().getName()}"></li>
        <li th:text="${'Time: ' + registration.getTimeSlots().getStandardTime()}"></li>
        <li th:text="${'Meal: ' + (registration.getMealOptions() != null ? registration.getMealOptions().getSignUpName() : 'Own')}"></li>
    </ul>
</th:block>

Parenthesis surrounding the conditional operation.