Massimo Scola
asked on
Spring MVC: Saving Enum from JSP to Bean
I work in an industry where we work closely with publishing houses and publications. We use the Spring MVC framework with the usual controllers, services and views (currently JSP). I was tasked to write an internal marketing application that will send automatic emails to various publications (“Mediums”) whenever they or one of their books/magazines is mentioned on our website. The application will have to save the medium name and standard emails texts in several languages.
Controller
When a new record has to be made, the controller executes the following code and returns the view. The language is passed as a model attribute.
Language Enum
Language is an Enum class that contains all the languages used by our company.
AttributeConverter
As I am dealing with Enums, I created an attribute converter that converts from String to Language and vice versa.
The Bean
The bean (or form as we call it) contains the following fields. There are no getters/setters as it is handled by Lombok (@Data).
The view receives all the languages, loops through all languages
The problem is, that the languages cannot be displayed as there is a problem with the JSP.
I have attached a screenshot of what the application should look like.
Is the problem that I cannot pass an Enum in Map from the view (JSP) to the Bean?
Any suggestions on how to solve this?
Thanks for your help
Massimo
The Files
Controller
When a new record has to be made, the controller executes the following code and returns the view. The language is passed as a model attribute.
@GetMapping(path="/new")
public String newMedium(HttpServletRequest request, Model model, @ModelAttribute(name = "bookMarketingForm") BookMarketingMediumForm bookMarketingForm) throwsBaseException
{
model.addAttribute("allLanguages", getAllLanguages());
return "pps/bookmarketing/bookmarketing-save";
}
@ModelAttribute("allLanguages")
protected Language[] getAllLanguages()
{
return Language.values();
}
Language Enum
Language is an Enum class that contains all the languages used by our company.
public enum Language
{
EN(new Locale("en", "US"), true),
DE(new Locale("de", "DE"), true),
ES(new Locale("es", "ES"), true),
RU(new Locale("ru", "RU"), false),
ZH(new Locale("zh", "CN"), false),
PT(new Locale("pt", "BR"), true),
FR(new Locale("fr", "FR"), true);
/**
* Find the best suitable supported Language for the given ISO code.
* Defaults to Language.EN if the isoCode is null, not supported or malformed.
* @param isoCode either a two letter ISO language code or an IETF BCP 47 language tag string as described in {@link Locale#forLanguageTag(String)}. <code>null</code> defaults to Language.EN.
* @return always a Language
*/
public static Language ofIsoCode(String isoCode)
{
if (isoCode == null)
{
return Language.EN;
}
if (isoCode.length() == 2)
{
return Optional.ofNullable(EnumUtils.getEnum(Language.class, isoCode.toUpperCase())).orElse(Language.EN);
}
else if (isoCode.length() > 2)
{
final Locale requestedLocale = Locale.forLanguageTag(isoCode.replaceAll("_", "-"));
return ofLocale(requestedLocale);
}
else
{
return Language.EN;
}
}
/**
* Find the best suitable supported Language for the given Locale.
* Defaults to Language.EN.
* @param locale <code>null</code> defaults to Language.EN
* @return always a Language
*/
public static Language ofLocale(Locale locale)
{
if (locale == null) {
return Language.EN;
} else {
return Arrays.stream(Language.values())
.filter(l -> l.locale.getLanguage().equals(locale.getLanguage()))
.findFirst()
.orElse(Language.EN);
}
}
private final Locale locale;
public final boolean latinScript;
private Language(Locale locale, boolean latinScript)
{
this.locale = locale;
this.latinScript = latinScript;
}
/**
* Return the two-letter ISO language code.
* @return a two character ASCII String like "en";
*/
@JsonValue
public String getIsoCode()
{
return locale.getLanguage();
}
/**
* Return the corresponding Java Locale including the country
* code of the language variant we support.
* @return a Java locale like <code>en_EN</code>.
*/
public Locale getLocale()
{
return locale;
}
/**
* Whether or not the language uses latin alphabet. Useful to decide on fonts.
* @return true if the language uses primarily latin alphabet characters, false otherwise.
*/
public boolean isLatinScript()
{
return latinScript;
}
/**
* The two-letter lower-case ISO language code for the language.
*/
@Override
public String toString()
{
return getIsoCode();
}
AttributeConverter
As I am dealing with Enums, I created an attribute converter that converts from String to Language and vice versa.
@Converter(autoApply = true)
publicclass BookMarketingMediumEmailLanguageConverter implements AttributeConverter<Language, String> {
@Override
public String convertToDatabaseColumn(Language attribute) {
return attribute.getIsoCode();
}
@Override
public Language convertToEntityAttribute(String isoCode) {
return Language.ofIsoCode(isoCode);
}
}
The Bean
The bean (or form as we call it) contains the following fields. There are no getters/setters as it is handled by Lombok (@Data).
@Data
publicclass BookMarketingMediumForm
{
private int bookMarketingMediumId;
private @Resource Map<Language, BookMarketingMediumEmailView> bookMarketingMediumEmails = new TreeMap<>();
@NotBlank
private String bookMarketingMediumDE;
@NotBlank
private String bookMarketingMediumEN;
}
The ViewThe view receives all the languages, loops through all languages
<c:forEach var="language" items="${allLanguages}">
<c:set var="bookMarketingLanguage" value="${language}" />
<form:input type="textarea" path="bookMarketingMediumEmails[${language}].email" label="Email text" rows="8" cssStyle="margin-bottom: 15px"/>
<form:input type="text" path="bookMarketingMediumEmails[${language}].subject" label="Email Subject" maxlength="40"/>
</c:forEach>
The Problem
The problem is, that the languages cannot be displayed as there is a problem with the JSP.
2020-10-25 18:48:47,833
JAVA org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'bookMarketingMediumEmails[en]' of bean class
[com.getabstract.web.pps.bookmarketing.BookMarketingMediumForm]: Invalid index in property path 'bookMarketingMediumEmails[en]'; nested exception is
org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'com.getabstract.common.util.i18n.Language' for property 'null';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [com.getabstract.common.util.i18n.Language] for value 'en';
nested exception is java.lang.IllegalArgumentException: No enum constant com.getabstract.common.util.i18n.Language.en
I have attached a screenshot of what the application should look like.
Is the problem that I cannot pass an Enum in Map from the view (JSP) to the Bean?
Any suggestions on how to solve this?
Thanks for your help
Massimo
ASKER
Isn't this supposed to be handled by the Language Enum?
As you can see, I'm not hardcoding anything.
I still that the problem is converting the string in jsp to an enum, but I'm open for suggestions.
As you can see, I'm not hardcoding anything.
I still that the problem is converting the string in jsp to an enum, but I'm open for suggestions.
I still that the problem is converting the string in jsp to an enum, but I'm open for suggestions.I assume you mean "think". If so, that's exactly my point:
public class EnumTest {
enum Foo {
EN;
}
public static void main(String[] args) {
System.out.println(Foo.valueOf("EN"));
System.out.println(Foo.valueOf("en"));
}
}
This question needs an answer!
Become an EE member today
7 DAY FREE TRIALMembers can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Just a thought (i don't do Spring) but wouldn't that be case-sensitive? If so, it might need to be 'EN' not 'en'