Link to home
Start Free TrialLog in
Avatar of Massimo Scola
Massimo ScolaFlag for Switzerland

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.
 

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();
}

Open in new window



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 &quot;en&quot;;
    */
   @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();
   }

Open in new window


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);
       }
 }

Open in new window


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;
}

Open in new window

The View

The 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>

Open in new window


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

Open in new window


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

User generated image
Avatar of CEHJ
CEHJ
Flag of United Kingdom of Great Britain and Northern Ireland image

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'; 

Open in new window


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'
Avatar of Massimo Scola

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. 
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"));
    }
}

Open in new window

This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members 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.