3

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).

kflorence
  • 2,187
  • 2
  • 16
  • 15

1 Answers1

0

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))
    )
  }
}
kflorence
  • 2,187
  • 2
  • 16
  • 15