Skip to content

Instantly share code, notes, and snippets.

@miensol
Created December 12, 2017 09:29
Show Gist options
  • Save miensol/cca73d158ce8e7664ed653a30fc8dc70 to your computer and use it in GitHub Desktop.
Save miensol/cca73d158ce8e7664ed653a30fc8dc70 to your computer and use it in GitHub Desktop.
class CommandInvocableHandlerMethod(private val handlerMethod: HandlerMethod,
private val requestCommandFactory: RequestCommandFactory,
private val configuration: RequestCommandConfiguration) : ServletInvocableHandlerMethod(handlerMethod) {
private lateinit var returnValueHandlers: HandlerMethodReturnValueHandlerComposite
@Trace(dispatcher = true)
override fun invokeForRequest(request: NativeWebRequest?, mavContainer: ModelAndViewContainer?, vararg providedArgs: Any?): Any {
// same as super.invokeForRequest(request, mavContainer, *providedArgs)
//but with request passed to do invoke
val args = this.getMethodArgumentValuesCallable.invoke(request, mavContainer, providedArgs)
val result = doInvokeWithRequest(request, args)
return result
}
private fun doInvokeWithRequest(request: NativeWebRequest?, args: Array<out Any?>?): Any {
val nativeRequest = request?.getNativeRequest(HttpServletRequest::class.java)
// If the response has already set error status code tomcat will not wait for async result
// not sure how we should only handle DispatcherType.REQUEST
return if (nativeRequest != null && nativeRequest.dispatcherType == DispatcherType.REQUEST) {
val callSuper = Callable {
super.doInvoke(*(args ?: emptyArray()))
}
val job = callSuper
val context = RequestCommandContext(configuration, handlerMethod, SecurityContextHolder.getContext(), job)
val result = requestCommandFactory.createSingle(context)
MonoDeferredResult(result)
} else {
super.doInvoke(*(args ?: emptyArray()))
}
}
override fun setHandlerMethodReturnValueHandlers(returnValueHandlers: HandlerMethodReturnValueHandlerComposite?) {
this.returnValueHandlers = returnValueHandlers!!
super.setHandlerMethodReturnValueHandlers(returnValueHandlers)
}
override fun wrapConcurrentResult(result: Any?): ServletInvocableHandlerMethod {
return ConcurrentResultHandlerMethod(result, ConcurrentResultMethodParameter(result))
}
private val getMethodArgumentValuesMethod = InvocableHandlerMethod::class.members
.single { it.name == "getMethodArgumentValues" }
.apply { isAccessible = true }
//Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs)
private val getMethodArgumentValuesCallable: (request: NativeWebRequest?, mavContainer: ModelAndViewContainer?, args: Array<out Any?>) -> Array<out Any?>?
= { request, mavContainer, args ->
val receiver = this@CommandInvocableHandlerMethod
try {
getMethodArgumentValuesMethod.call(receiver, request, mavContainer, args) as Array<out Any?>?
} catch (ex: InvocationTargetException) {
LOG.debug("Invocation failed for $handlerMethod")
throw ex.cause ?: ex
}
}
private inner class ConcurrentResultHandlerMethod(result: Any?, returnType: CommandInvocableHandlerMethod.ConcurrentResultMethodParameter) : ServletInvocableHandlerMethod(Callable {
if (result is Exception) {
throw result
} else if (result is Throwable) {
throw NestedServletException("Async processing failed", result)
}
result
}, CALLABLE_METHOD) {
private val _returnType: MethodParameter
init {
setHandlerMethodReturnValueHandlers([email protected])
this._returnType = returnType
}
/**
* Bridge to actual controller type-level annotations.
*/
override fun getBeanType(): Class<*>? {
return [email protected]()
}
/**
* Bridge to actual return value or generic type within w the declared
* async return type, e.g. Foo instead of `DeferredResult<Foo>`.
*/
override fun getReturnValueType(returnValue: Any?): MethodParameter? {
return this._returnType
}
/**
* Bridge to controller method-level annotations.
*/
override fun <A : Annotation> getMethodAnnotation(annotationType: Class<A>): A? {
return [email protected](annotationType)
}
// next spring version
/**
* Bridge to controller method-level annotations.
*/
override fun <A : Annotation> hasMethodAnnotation(annotationType: Class<A>): Boolean {
return [email protected](annotationType)
}
}
/**
* MethodParameter subclass based on the actual return value type or if
* that's null falling back on the generic type within the declared async
* return type, e.g. Foo instead of `DeferredResult<Foo>`.
*/
private inner class ConcurrentResultMethodParameter : HandlerMethod.HandlerMethodParameter {
private val returnValue: Any?
private val returnType: ResolvableType?
constructor(returnValue: Any?) : super(-1) {
this.returnValue = returnValue
this.returnType = if (returnValue is DeferredResult<*> || returnValue is Callable<*> || returnValue is Future<*>) {
val genericParameterType = super.getGenericParameterType()
ResolvableType.forType(genericParameterType).getGeneric(0)
} else {
null
}
}
constructor(original: ConcurrentResultMethodParameter) : super(original) {
this.returnValue = original.returnValue
this.returnType = original.returnType
}
override fun getParameterType(): Class<*>? {
if (this.returnValue != null) {
return this.returnValue.javaClass
}
if (this.returnType != null && ResolvableType.NONE != this.returnType) {
return this.returnType.resolve()
}
return super.getParameterType()
}
override fun getGenericParameterType(): Type? {
//This might not be correct, but from what I've checked works as expected
return this.returnType?.type ?: parameterType
}
// next spring version
override fun clone(): ConcurrentResultMethodParameter {
return ConcurrentResultMethodParameter(this)
}
}
companion object {
val CALLABLE_METHOD = Callable<*>::call.javaMethod
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment