kflorence kflorence - 3 months ago 14
Scala Question

Spring RequestParam formatter for Scala.Option

We are using Spring MVC in our Scala application and I would like to figure out how to unwrap Scala

Option
's so they can be properly converted using
@RequestParam
. I think the solution may have something to do with the Formatter SPI, but I am unsure how to get this to work nicely given that
Option
's can contain any number of values (which I'd want Spring to handle normally, as if the converted value were not an
Option
at all). In essence, I'd almost want to apply an additional conversion of a value to be
Option
wrapped after normal conversion takes place.

For example, given the following code:

@RequestMapping(method = Array(GET), value = Array("/test"))
def test(@RequestParam("foo") foo: Option[String]): String


The url
/test
should result in the
foo
parameter getting a value of
None
, while the url
/test?foo=bar
should result in the
foo
parameter getting a value of
Some("bar")
(
/test?foo
could result in either an empty String, or
None
).

Answer

We managed to solve this by creating an AnyRef to Option[AnyRef] converter and adding it to Spring MVC's ConversionService:

import org.springframework.beans.factory.annotation.{Autowired, Qualifier}
import org.springframework.core.convert.converter.ConditionalGenericConverter
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
import org.springframework.core.convert.{ConversionService, TypeDescriptor}
import org.springframework.stereotype.Component

import scala.collection.convert.WrapAsJava

/**
 * Base functionality for option conversion.
 */
trait OptionConverter extends ConditionalGenericConverter with WrapAsJava {
  @Autowired
  @Qualifier("mvcConversionService")
  var conversionService: ConversionService = _
}

/**
 * Converts `AnyRef` to `Option[AnyRef]`.
 * See implemented methods for descriptions.
 */
@Component
class AnyRefToOptionConverter extends OptionConverter {
  override def convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): AnyRef = {
    Option(source).map(s => conversionService.convert(s, sourceType, new Conversions.GenericTypeDescriptor(targetType)))
  }

  override def getConvertibleTypes: java.util.Set[ConvertiblePair] = Set(
    new ConvertiblePair(classOf[AnyRef], classOf[Option[_]])
  )

  override def matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean = {
    Option(targetType.getResolvableType).forall(resolvableType =>
      conversionService.canConvert(sourceType, new Conversions.GenericTypeDescriptor(targetType))
    )
  }
}
Comments